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();
|
const app = loopback();
|
||||||
|
|
||||||
app.set('state namespace', '__fcc__');
|
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.set('views', path.join(__dirname, 'views'));
|
||||||
app.use(loopback.token());
|
app.use(loopback.token());
|
||||||
app.use(
|
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 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');
|
const envPath = path.resolve(__dirname, '../.env');
|
||||||
if (!fs.existsSync(envPath)) {
|
const { error } = require('dotenv').config({ path: envPath });
|
||||||
throw Error('.env not found, please copy sample.env to .env.');
|
|
||||||
|
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 {
|
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
|
# Database
|
||||||
MONGOHQ_URL='mongodb://localhost:27017/freecodecamp'
|
MONGOHQ_URL=mongodb://localhost:27017/freecodecamp
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
SENTRY_DSN=dsn_from_sentry_dashboard
|
SENTRY_DSN=dsn_from_sentry_dashboard
|
||||||
SENTRY_ENVIRONMENT='staging'
|
SENTRY_ENVIRONMENT=staging
|
||||||
|
|
||||||
# Auth0 - OAuth 2.0 Credentials
|
# Auth0 - OAuth 2.0 Credentials
|
||||||
AUTH0_CLIENT_ID=client_id_from_auth0_dashboard
|
AUTH0_CLIENT_ID=client_id_from_auth0_dashboard
|
||||||
@ -39,17 +39,15 @@ STRIPE_SECRET_KEY=sk_from_stripe_dashboard
|
|||||||
# PayPal
|
# PayPal
|
||||||
PAYPAL_CLIENT_ID=id_from_paypal_dashboard
|
PAYPAL_CLIENT_ID=id_from_paypal_dashboard
|
||||||
PAYPAL_SECRET=secret_from_paypal_dashboard
|
PAYPAL_SECRET=secret_from_paypal_dashboard
|
||||||
PAYPAL_VERIFY_WEBHOOK_URL='https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature'
|
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_API_TOKEN_URL=https://api.sandbox.paypal.com/v1/oauth2/token
|
||||||
PAYPAL_WEBHOOK_ID=webhook_id_from_paypal_dashboard
|
PAYPAL_WEBHOOK_ID=webhook_id_from_paypal_dashboard
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# Build variants
|
# Build variants
|
||||||
# ---------------------
|
# ---------------------
|
||||||
|
DEPLOYMENT_ENV=staging
|
||||||
PIPELINE_ENV=false
|
FREECODECAMP_NODE_ENV=development
|
||||||
DEPLOYMENT_ENV='staging'
|
|
||||||
FREECODECAMP_NODE_ENV='development'
|
|
||||||
|
|
||||||
# Languages to build
|
# Languages to build
|
||||||
CLIENT_LOCALE=english
|
CLIENT_LOCALE=english
|
||||||
@ -59,11 +57,11 @@ CURRICULUM_LOCALE=english
|
|||||||
SHOW_UPCOMING_CHANGES=false
|
SHOW_UPCOMING_CHANGES=false
|
||||||
|
|
||||||
# Application paths
|
# Application paths
|
||||||
HOME_LOCATION='http://localhost:8000'
|
HOME_LOCATION=http://localhost:8000
|
||||||
API_LOCATION='http://localhost:3000'
|
API_LOCATION=http://localhost:3000
|
||||||
FORUM_LOCATION='https://forum.freecodecamp.org'
|
FORUM_LOCATION=https://forum.freecodecamp.org
|
||||||
NEWS_LOCATION='https://www.freecodecamp.org/news'
|
NEWS_LOCATION=https://www.freecodecamp.org/news
|
||||||
RADIO_LOCATION='https://coderadio.freecodecamp.org'
|
RADIO_LOCATION=https://coderadio.freecodecamp.org
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# Debugging Mode Keys
|
# Debugging Mode Keys
|
||||||
@ -74,4 +72,4 @@ DEBUG=true
|
|||||||
LOCAL_MOCK_AUTH=true
|
LOCAL_MOCK_AUTH=true
|
||||||
|
|
||||||
# Webhook proxy url from smee.io for PayPal
|
# 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