feat: add Docker build (#41187)
* feat(docker): build and use client and api images * feat: always use .env dotenv fails without throwing if the .env file is missing and never overwrites variables if they already exist. As such, we can use it in build pipelines. * fix: remove quotes from env vars dotenv normalises quoted and unquoted strings (X=x, X='x' and X="x") all become the same .env object {X: 'x'}. However, Docker's env_file does not (the three cases are distinct). As a result, we should use unquoted strings for consistency. * fix: provide custom warning when .env is missing * feat(docker): include client-config * fix(docker): remove build packages from api image * fix(docker): run script from correct dir * fix(docker): correct permissions and dests * fix(docker): consolidate run steps This is standard practice, but did not have a noticable affect on the image size * fix(docker): clean the npm cache Prior to this step the image was 1.11GB uncompressed and we got a modest saving, 1.09GB after. * refactor(docker): regexless COPY directives * feat(docker): use alpine This shrinks the image down to 259MB * fix(docker): update build scripts * fix: correct the server Dockerfile RUNs * DEBUG: expose mysql port for seeding * chore: update client Dockerfile's node versions * fix: remove executable permissions from index.js It's not a cli, so I don't think it needs to be executable. * chore: update node and remove stale comments * feat: use ENTRYPOINT + CMD to allow runtime config * fix: add CURRICULUM_LOCALE arg * feat: allow client port configuration * feat: allow api port to be configured * refactor: use unique variable names for ports * fix: add default CLIENT_PORT * refactor: clean up
This commit is contained in:
committed by
GitHub
parent
0858f078e2
commit
5d46e2830a
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@ -0,0 +1,9 @@
|
||||
client/.cache
|
||||
client/public
|
||||
.env
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
*Dockerfile*
|
||||
*docker-compose*
|
||||
**/node_modules
|
2
api-server/src/server/index.js
Executable file → Normal file
2
api-server/src/server/index.js
Executable file → Normal file
@ -32,7 +32,7 @@ Rx.config.longStackSupport = process.env.NODE_DEBUG !== 'production';
|
||||
const app = loopback();
|
||||
|
||||
app.set('state namespace', '__fcc__');
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
app.set('port', process.env.API_PORT || 3000);
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.use(loopback.token());
|
||||
app.use(
|
||||
|
40
client.Dockerfile
Normal file
40
client.Dockerfile
Normal file
@ -0,0 +1,40 @@
|
||||
FROM node:14.16.1-buster AS builder
|
||||
|
||||
# this is a bit clunky, perhaps there's a more concise way of passing in build
|
||||
# arguments
|
||||
ARG FREECODECAMP_NODE_ENV
|
||||
ARG HOME_LOCATION
|
||||
ARG API_LOCATION
|
||||
ARG FORUM_LOCATION
|
||||
ARG NEWS_LOCATION
|
||||
ARG CLIENT_LOCALE
|
||||
ARG CURRICULUM_LOCALE
|
||||
ARG STRIPE_PUBLIC_KEY
|
||||
ARG ALGOLIA_APP_ID
|
||||
ARG ALGOLIA_API_KEY
|
||||
ARG PAYPAL_CLIENT_ID
|
||||
ARG DEPLOYMENT_ENV
|
||||
ARG SHOW_UPCOMING_CHANGES
|
||||
|
||||
# node images create a non-root user that we can use
|
||||
USER node
|
||||
WORKDIR /home/node/build
|
||||
COPY --chown=node:node . .
|
||||
RUN npm ci
|
||||
# we don't need to separately run ensure-env, since it gets called as part of
|
||||
# build:client
|
||||
RUN npm run build:client
|
||||
|
||||
WORKDIR /home/node/config
|
||||
RUN git clone https://github.com/freeCodeCamp/client-config.git client
|
||||
|
||||
FROM node:14.16.1-alpine
|
||||
RUN npm i -g serve
|
||||
USER node
|
||||
WORKDIR /home/node
|
||||
COPY --from=builder /home/node/build/client/public/ client/public
|
||||
COPY --from=builder /home/node/config/client/serve.json client
|
||||
COPY --from=builder /home/node/config/client/www/ client
|
||||
|
||||
ENTRYPOINT ["serve", "-c", "../serve.json", "client/public"]
|
||||
CMD ["-l", "8000"]
|
@ -1,13 +1,15 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// PIPELINE_ENV is 'true' in the build pipeline
|
||||
if (process.env.PIPELINE_ENV !== 'true') {
|
||||
const envPath = path.resolve(__dirname, '../.env');
|
||||
if (!fs.existsSync(envPath)) {
|
||||
throw Error('.env not found, please copy sample.env to .env.');
|
||||
const envPath = path.resolve(__dirname, '../.env');
|
||||
const { error } = require('dotenv').config({ path: envPath });
|
||||
|
||||
if (error) {
|
||||
if (process.env.FREECODECAMP_NODE_ENV === 'development') {
|
||||
console.warn('.env not found, please copy sample.env to .env');
|
||||
} else {
|
||||
console.warn(`.env not found. If env vars are not being set another way,
|
||||
this could be a problem.`);
|
||||
}
|
||||
require('dotenv').config({ path: envPath });
|
||||
}
|
||||
|
||||
const {
|
||||
|
8
docker-compose.api.yml
Normal file
8
docker-compose.api.yml
Normal file
@ -0,0 +1,8 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
api:
|
||||
env_file: .env
|
||||
image: fcc_api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '${API_PORT:-3000}:${API_PORT:-3000}'
|
8
docker-compose.client.yml
Normal file
8
docker-compose.client.yml
Normal file
@ -0,0 +1,8 @@
|
||||
version: '3.7'
|
||||
services:
|
||||
client:
|
||||
image: fcc_client
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '${CLIENT_PORT:-8000}:${CLIENT_PORT:-8000}'
|
||||
command: ["-l", "${CLIENT_PORT:-8000}"]
|
26
sample.env
26
sample.env
@ -3,11 +3,11 @@
|
||||
# ---------------------
|
||||
|
||||
# Database
|
||||
MONGOHQ_URL='mongodb://localhost:27017/freecodecamp'
|
||||
MONGOHQ_URL=mongodb://localhost:27017/freecodecamp
|
||||
|
||||
# Logging
|
||||
SENTRY_DSN=dsn_from_sentry_dashboard
|
||||
SENTRY_ENVIRONMENT='staging'
|
||||
SENTRY_ENVIRONMENT=staging
|
||||
|
||||
# Auth0 - OAuth 2.0 Credentials
|
||||
AUTH0_CLIENT_ID=client_id_from_auth0_dashboard
|
||||
@ -39,17 +39,15 @@ STRIPE_SECRET_KEY=sk_from_stripe_dashboard
|
||||
# PayPal
|
||||
PAYPAL_CLIENT_ID=id_from_paypal_dashboard
|
||||
PAYPAL_SECRET=secret_from_paypal_dashboard
|
||||
PAYPAL_VERIFY_WEBHOOK_URL='https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature'
|
||||
PAYPAL_API_TOKEN_URL='https://api.sandbox.paypal.com/v1/oauth2/token'
|
||||
PAYPAL_VERIFY_WEBHOOK_URL=https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature
|
||||
PAYPAL_API_TOKEN_URL=https://api.sandbox.paypal.com/v1/oauth2/token
|
||||
PAYPAL_WEBHOOK_ID=webhook_id_from_paypal_dashboard
|
||||
|
||||
# ---------------------
|
||||
# Build variants
|
||||
# ---------------------
|
||||
|
||||
PIPELINE_ENV=false
|
||||
DEPLOYMENT_ENV='staging'
|
||||
FREECODECAMP_NODE_ENV='development'
|
||||
DEPLOYMENT_ENV=staging
|
||||
FREECODECAMP_NODE_ENV=development
|
||||
|
||||
# Languages to build
|
||||
CLIENT_LOCALE=english
|
||||
@ -59,11 +57,11 @@ CURRICULUM_LOCALE=english
|
||||
SHOW_UPCOMING_CHANGES=false
|
||||
|
||||
# Application paths
|
||||
HOME_LOCATION='http://localhost:8000'
|
||||
API_LOCATION='http://localhost:3000'
|
||||
FORUM_LOCATION='https://forum.freecodecamp.org'
|
||||
NEWS_LOCATION='https://www.freecodecamp.org/news'
|
||||
RADIO_LOCATION='https://coderadio.freecodecamp.org'
|
||||
HOME_LOCATION=http://localhost:8000
|
||||
API_LOCATION=http://localhost:3000
|
||||
FORUM_LOCATION=https://forum.freecodecamp.org
|
||||
NEWS_LOCATION=https://www.freecodecamp.org/news
|
||||
RADIO_LOCATION=https://coderadio.freecodecamp.org
|
||||
|
||||
# ---------------------
|
||||
# Debugging Mode Keys
|
||||
@ -74,4 +72,4 @@ DEBUG=true
|
||||
LOCAL_MOCK_AUTH=true
|
||||
|
||||
# Webhook proxy url from smee.io for PayPal
|
||||
WEBHOOK_PROXY_URL=''
|
||||
WEBHOOK_PROXY_URL=
|
||||
|
28
server.Dockerfile
Normal file
28
server.Dockerfile
Normal file
@ -0,0 +1,28 @@
|
||||
FROM node:14.16.1-alpine as builder
|
||||
USER node
|
||||
WORKDIR /home/node/build
|
||||
COPY --chown=node:node . .
|
||||
|
||||
RUN npm ci
|
||||
RUN npm run build:curriculum
|
||||
RUN npm run build:server
|
||||
|
||||
FROM node:14.16.1-alpine
|
||||
USER node
|
||||
WORKDIR /home/node/api
|
||||
# get and install deps
|
||||
COPY --from=builder --chown=node:node /home/node/build/package.json /home/node/build/package-lock.json ./
|
||||
COPY --from=builder --chown=node:node /home/node/build/api-server/package.json /home/node/build/api-server/package-lock.json api-server/
|
||||
RUN npm ci --production --ignore-scripts \
|
||||
&& cd api-server \
|
||||
&& npm ci --production \
|
||||
&& npm cache clean --force
|
||||
COPY --from=builder --chown=node:node /home/node/build/api-server/lib/ api-server/lib/
|
||||
COPY --from=builder --chown=node:node /home/node/build/utils/ utils/
|
||||
COPY --from=builder --chown=node:node /home/node/build/config/ config/
|
||||
|
||||
WORKDIR /home/node/api/api-server
|
||||
|
||||
CMD ["npm", "start"]
|
||||
|
||||
# TODO: don't copy mocks/fixtures
|
Reference in New Issue
Block a user