Skip to content

Commit fa47ac9

Browse files
committed
(flavor) docker v3.0.0
1 parent 839e7af commit fa47ac9

File tree

7 files changed

+247
-17
lines changed

7 files changed

+247
-17
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
server/.env*

DOCKER.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
## `docker` Flavor
2+
This flavor of the pal boilerplate is designed to get someone up and running
3+
using Docker with scalable best practices. Certain features require the `deployment`
4+
flavor of the pal boilerplate to be installed as well in order to function.
5+
6+
## Basics
7+
This [Dockerfile](./server/Dockerfile) uses
8+
[multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/)
9+
to create different Docker images depending on the environment the images will
10+
be running in, while ensuring best practices are followed to keep image size low
11+
and create a security sandbox if you are running a server.
12+
13+
We are using Docker Compose to define `web` and `test` services. The `web` wervice uses
14+
the `release` Dockerfile stage, and the `test` service uses the `test` Dockerfile stage.
15+
16+
### `docker-compose.yml` Services
17+
The `docker-compose.yml` file defines four services. The reason for the `*_base` services
18+
is that certain CI providers like CircleCI do not support voluming in files, and you will
19+
get strange file system errors if you try to run with a `volumes` block.
20+
21+
#### `web_base`
22+
All configuration for running the webserver locally, minus the `volumes` block.
23+
24+
#### `web`
25+
`web_base`, plus the `volumes` block required for hotreloading to function locally.
26+
27+
#### `test_base`
28+
All configuration for running the tests, minus the `volumes` block.
29+
30+
#### `test`
31+
`test_base`, plus the `volumes` block required to allow re-running tests in
32+
`watch` mode or by hand, but without requiring a full image rebuild.
33+
34+
### Commands
35+
36+
#### `npm run build:test`
37+
Rebuilds the `test` service used for running tests, and will re-install npm
38+
dependencies when they change.
39+
40+
#### `npm run build:web`
41+
Rebuilds the `web` service used locally, and will re-install npm dependencies when
42+
they change.
43+
44+
#### `npm run docker:lint`
45+
Runs the `npm run lint` command in the `test` service.
46+
47+
#### `npm run docker:start`
48+
Starts the `web` service in daemon mode, making it available at `localhost:3000`.
49+
50+
#### `npm run docker:test`
51+
Runs the `npm test` command in the `test` service.
52+
53+
#### `npm run docker:test:ci`
54+
Starts the `test_base` service, which triggers both a `lint` and `test` npm script
55+
run. The process will exit with the same code the internal process exits with,
56+
making this the ideal command to use when integrating with CI.
57+
58+
### `.env`
59+
This flavor no longer uses `.env` files, as it is safer to start the container declaring
60+
the environment variables in either the `docker-compose.yml` file or via the `docker` cli.
61+
62+
If you need to add any environment variables, you can do so in the `docker-compose.yml`
63+
files as shown, or add them to `.env` and load them in as shown
64+
[here](https://docs.docker.com/compose/compose-file/#env_file).
65+
66+
### Stages
67+
There are currently four stages in the Dockerfile that are used.
68+
69+
#### `base`
70+
Contains all package installs, files system modifications, and entrypoint declaration.
71+
Any commands that will not change across the other stages or are required for multiple
72+
other stages to execute properly should be put in this stage.
73+
74+
#### `dependencies`
75+
The `dependencies` stage is used to install npm dependencies, copy all of your source
76+
files, and then optionally run any build commands that need to be executed. The desired
77+
outcome of this stage is an image that contains all dependencies, all source files, and
78+
all built assets (if necessary).
79+
80+
In order to run build scripts, you will need to locate this stage in the Dockerfile
81+
and uncomment the `RUN npm run build` command that is in there, possibly changing the
82+
command if you are using multiple build scripts in your `package.json`.
83+
84+
#### `test`
85+
The `test` stage is used for running your test suite. It changes the user to `node`
86+
to mimic the production environment, and then runs `npm lint && npm test` which should
87+
run your full test suite. The container will exit with the same code as your test
88+
runner, which means that in CI you can run `npm run docker:test:ci` as your test step to
89+
automatically build your test image and then run your tests inside of it.
90+
91+
#### `development`
92+
The `development` stage is meant to run your server locally with dev deps installed. The
93+
`docker-compose.yml` file can be used to tweak the environment variables used for
94+
this stage when running locally, and the logic for building this stage should not
95+
make any assumptions about what environment the output will be used in.
96+
97+
#### `release`
98+
The `release` stage is meant to run your server in a production-like setup without dev deps.
99+
The `docker-compose.yml` file can be used to tweak the environment variables used for
100+
this stage when running locally, and the logic for building this stage should not
101+
make any assumptions about what environment the output will be used in.
102+
103+
## Local Development
104+
If you are using the `development` flavor alongside this `docker` flavor, you can simply
105+
run `npm run docker:start` to build the release stage of the Dockerfile, and then start
106+
it as a `daemon` in Docker. You can then visit `localhost:3000` to hit your server.
107+
108+
By default, the server is started using `nodemon` when you start it using
109+
`docker-compose`, which means every time you save a file the server will be restarted
110+
automatically with your fresh changes.
111+
112+
### Running Tests
113+
Running tests locally is extremely easy. Simply run `npm run docker:test`, and linting
114+
and testing will be run. If you modify the `package*.json` files, you will need to run
115+
`npm run build:test` to trigger a reinstall of your dependencies.
116+
117+
### Secret Management (`.env`)
118+
This removes the option to have a `.env` files for secrets locally, because those environment
119+
variables can now just be set in the `docker-compose.yml` file `environment` section. If
120+
you have true secrets that you need locally and cannot check into Git, you can re-create
121+
the `server/.env` file, and then add a key in the `*_base` services in `docker-compose.yml`
122+
that is simply `env_file: server/.env` at the same level in the yaml as the `environment` key.
123+
124+
## Changing the Port
125+
Currently the build process assumes that the port default of `3000` in
126+
`server/manifest.js` is being used. `server/Dockerfile` has an `EXPOSE` command that
127+
references this port, and `docker-compose.yml` also contains a mapping of port `3000`
128+
in the container to port `3000` on the host machine.

docker-compose.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
version: '3.4'
2+
services:
3+
web_base: &web_base
4+
build:
5+
context: .
6+
dockerfile: server/Dockerfile
7+
target: development
8+
command: [
9+
"./node_modules/.bin/nodemon",
10+
"--watch",
11+
"lib",
12+
"--watch",
13+
"server",
14+
"server/index.js"
15+
]
16+
environment:
17+
- NODE_ENV=development
18+
ports:
19+
- 3000:3000
20+
web:
21+
# hack for docker-compose v3 dropping `extends` support
22+
<< : *web_base
23+
# allows for hot-reloading of server
24+
volumes:
25+
- ./lib:/app/lib
26+
- ./server:/app/server
27+
- ./test:/app/test
28+
- ./package.json:/app/package.json
29+
test_base: &test_base
30+
build:
31+
context: .
32+
dockerfile: server/Dockerfile
33+
target: test
34+
test:
35+
# hack for docker-compose v3 dropping `extends` support
36+
<< : *test_base
37+
# allows for hot-reloading of server
38+
volumes:
39+
- ./lib:/app/lib
40+
- ./server:/app/server
41+
- ./test:/app/test
42+
- ./package.json:/app/package.json

package.json

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,35 @@
33
"version": "3.0.0",
44
"main": "lib/index.js",
55
"scripts": {
6+
"build:test": "docker-compose build test",
7+
"build:web": "docker-compose build web",
8+
"docker:lint": "docker-compose run test npm run lint",
9+
"docker:start": "docker-compose up -d web",
10+
"docker:test": "docker-compose run test npm test",
11+
"docker:test:ci": "docker-compose up --build test_base",
612
"start": "node server",
713
"test": "lab -a @hapi/code -L",
814
"lint": "eslint ."
915
},
1016
"dependencies": {
1117
"@hapi/boom": "9.x.x",
18+
"@hapi/glue": "8.x.x",
19+
"@hapi/hapi": "20.x.x",
20+
"@hapipal/confidence": "6.x.x",
1221
"@hapipal/haute-couture": "4.x.x",
22+
"@hapipal/toys": "3.x.x",
23+
"exiting": "6.x.x",
1324
"joi": "17.x.x"
1425
},
1526
"devDependencies": {
1627
"@hapi/code": "8.x.x",
1728
"@hapi/eslint-config-hapi": "13.x.x",
1829
"@hapi/eslint-plugin-hapi": "4.x.x",
19-
"@hapi/glue": "8.x.x",
20-
"@hapi/hapi": "20.x.x",
2130
"@hapi/lab": "24.x.x",
22-
"@hapipal/confidence": "6.x.x",
2331
"@hapipal/hpal": "3.x.x",
2432
"@hapipal/hpal-debug": "2.x.x",
25-
"@hapipal/toys": "3.x.x",
2633
"babel-eslint": "10.x.x",
27-
"dotenv": "8.x.x",
2834
"eslint": "7.x.x",
29-
"exiting": "6.x.x"
35+
"nodemon": "2.x.x"
3036
}
3137
}

server/.env-keep

Lines changed: 0 additions & 6 deletions
This file was deleted.

server/Dockerfile

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# create base image with all packages, proper entrypoint, and directories created
2+
FROM node:14-alpine AS base
3+
4+
# install any packages we need from apt here
5+
RUN apk add --update dumb-init
6+
7+
# set entrypoint to `dumb-init` as it handles being pid 1 and forwarding signals
8+
# so that you dont need to bake that logic into your node app
9+
ENTRYPOINT ["dumb-init", "--"]
10+
11+
# all of our code will live in `/app`
12+
WORKDIR /app
13+
14+
# using the base image, create an image containing all of our files
15+
# and dependencies installed, devDeps and test directory included
16+
FROM base AS dependencies
17+
18+
COPY package*.json ./
19+
RUN npm set progress=false \
20+
&& npm config set depth 0 \
21+
&& npm i
22+
23+
COPY .eslintrc.json .
24+
COPY ./server ./server
25+
COPY ./test ./test
26+
COPY ./lib ./lib
27+
28+
# if you have any build scripts to run, like for the `templated-site` flavor
29+
# uncomment and possibly modify the following RUN command:
30+
# RUN npm run build
31+
# keeping all of the bash commands you can within a single RUN is generally important,
32+
# but for this case it's likely that we want to use the cache from the prune which will
33+
# change infrequently.
34+
35+
# test running image using all of the files and devDeps
36+
FROM dependencies AS test
37+
ENV NODE_ENV=test
38+
# use `sh -c` so we can chain test commands using `&&`
39+
CMD ["npm", "test"]
40+
41+
# dev ready image, contains devDeps for test running and debugging
42+
FROM dependencies AS development
43+
44+
# expose port 3000 from in the container to the outside world
45+
# this is the default port defined in server/manifest.js, and
46+
# will need to be updated if you change the default port
47+
EXPOSE 3000
48+
CMD ["npm", "start"]
49+
50+
# release ready image, devDeps are pruned and tests removed for size control
51+
FROM development AS release
52+
53+
ENV NODE_ENV=production
54+
55+
# prune non-prod dependencies, remove test files
56+
RUN npm prune --production \
57+
&& rm -rf ./test
58+
59+
# `node` user is created in base node image
60+
# we want to use this non-root user for running the server in case of attack
61+
# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user
62+
USER node

server/manifest.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
'use strict';
22

3-
const Dotenv = require('dotenv');
43
const Confidence = require('@hapipal/confidence');
54
const Toys = require('@hapipal/toys');
65

7-
// Pull .env into process.env
8-
Dotenv.config({ path: `${__dirname}/.env` });
9-
106
// Glue manifest as a confidence store
117
module.exports = new Confidence.Store({
128
server: {
13-
host: 'localhost',
9+
host: '0.0.0.0',
1410
port: {
1511
$param: 'PORT',
1612
$coerce: 'number',

0 commit comments

Comments
 (0)