Skip to content

Commit

Permalink
Fixes, docs, and example tweaks for 2022 (#101)
Browse files Browse the repository at this point in the history
* little tweaks

* npm ci now works as expected

* update linters

* fixing lints
  • Loading branch information
BretFisher authored Sep 20, 2022
1 parent 050927f commit 5da9c6c
Show file tree
Hide file tree
Showing 7 changed files with 694 additions and 1,840 deletions.
36 changes: 35 additions & 1 deletion .github/linters/.hadolint.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# README: https://github.com/hadolint/hadolint

# Often it's a good idea to do inline disables rather that repo-wide in this file.
# Example of inline Dockerfile rules:
# hadolint ignore=DL3018
#RUN apk add --no-cache git

failure-threshold: warning

# or just ignore rules repo-wide
ignored:
- DL3003 #ignore that we use cd sometimes
- DL3006 #image pin versions
Expand All @@ -7,4 +17,28 @@ ignored:
- DL3028 #gem install pin versions
- DL3059 #multiple consecutive runs
- DL4006 #we don't need pipefail in this
- SC2016 #we want single quotes sometimes
- SC2016 #we want single quotes sometimes


# FULL TEMPLATE
# failure-threshold: string # name of threshold level (error | warning | info | style | ignore | none)
# format: string # Output format (tty | json | checkstyle | codeclimate | gitlab_codeclimate | gnu | codacy)
# ignored: [string] # list of rules
# label-schema: # See Linting Labels below for specific label-schema details
# author: string # Your name
# contact: string # email address
# created: timestamp # rfc3339 datetime
# version: string # semver
# documentation: string # url
# git-revision: string # hash
# license: string # spdx
# no-color: boolean # true | false
# no-fail: boolean # true | false
# override:
# error: [string] # list of rules
# warning: [string] # list of rules
# info: [string] # list of rules
# style: [string] # list of rules
# strict-labels: boolean # true | false
# disable-ignore-pragma: boolean # true | false
# trustedRegistries: string | [string] # registry or list of registries
8 changes: 5 additions & 3 deletions .github/linters/.markdown-lint.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# MD013/line-length - Line length
MD013:
# Number of characters
line_length: 150
# Number of characters, default is 80
# I'm OK with long lines. All editors now have wordwrap
line_length: 9999
# Number of characters for headings
heading_line_length: 100
code_blocks: false
# check code blocks?
code_blocks: false
53 changes: 53 additions & 0 deletions .github/linters/.yaml-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
###########################################
# These are the rules used for #
# linting all the yaml files in the stack #
# NOTE: #
# You can disable line with: #
# # yamllint disable-line #
###########################################
rules:
braces:
level: warning
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: 0
max-spaces-inside-empty: 5
brackets:
level: warning
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: 0
max-spaces-inside-empty: 5
colons:
level: warning
max-spaces-before: 0
max-spaces-after: 1
commas:
level: warning
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
comments: disable
comments-indentation: disable
document-end: disable
document-start: disable
empty-lines:
level: warning
max: 2
max-start: 0
max-end: 0
hyphens:
level: warning
max-spaces-after: 1
indentation:
level: warning
spaces: consistent
indent-sequences: true
check-multi-line-strings: false
key-duplicates: enable
line-length: disable
new-line-at-end-of-file: disable
new-lines:
type: unix
trailing-spaces: disable
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# if you're doing anything beyond your local machine, please pin this to a specific version at https://hub.docker.com/_/node/
# FROM node:14-alpine also works here for a smaller image
FROM node:14-slim
# FROM node:14-alpine also works here for a smaller image (But I prefer the more reliable debian slim)
FROM node:16-slim

# set our node environment, either development or production
# defaults to production, compose overrides this to development on build and run
Expand All @@ -26,7 +26,7 @@ WORKDIR /opt/node_app
# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user
USER node
COPY --chown=node:node package.json package-lock.json* ./
RUN npm install --no-optional && npm cache clean --force
RUN npm ci && npm cache clean --force
ENV PATH /opt/node_app/node_modules/.bin:$PATH

# check every 30s to ensure this service returns HTTP 200
Expand Down
51 changes: 25 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,40 @@ I also have more about everything Docker and Node.js in my 8 hour video course [
## Local Development Features

- **Dev as close to prod as you can**.
docker-compose builds a local development image that is just like production image except for the
below dev-only features needed in image.
The goal is to have dev env be as close to test and prod as possible while still giving all the
Docker Compose builds a local development image that is just like the production image except for the
below dev-only features needed in the image.
The goal is to have dev environment be as close to test and prod as possible while still giving all the
nice tools to make you a happy dev.
- **Prevent needing node/npm on host**.
Installs `node_modules` outside app root in container so local development won't run into a
This installs `node_modules` outside app root in the container image so local development won't run into a
problem of bind-mounting over it with local source code. This means it will run `npm install`
once on container build and you don't need to run npm on host or on each docker run.
It will re-run on build if you change `package.json`.
- **One line startup**. Uses `docker-compose up` for single-line build and run of local
- **One line startup**. Uses `docker compose up` for single-line build and run of local
development server.
- **Edit locally while code runs in container**.
docker-compose uses proper bind-mounts of host source code into container so you can edit
Docker Compose uses proper bind-mounts of host source code into container so you can edit
locally while running code in Linux container.
- **Use nodemon in container**. docker-compose uses nodemon for development for auto-restarting
- **Use nodemon in container**. Docker Compose uses nodemon for development for auto-restarting
Node.js in container when you change files on host.
- **Enable debug from host to container**. opens the inspect port 9229 for using host-based
debugging like chrome tools or VS Code. Nodemon enables `--inspect` by default in docker-compose.
- **Enable debug from host to container**. Opens the inspect port 9229 for using host-based
debugging like chrome tools or VS Code. Nodemon enables `--inspect` by default in Docker Compose.
- **Provides VSCode debug configs and tasks for tests**. for Visual Studio Code fans,
`.vscode` directory has the goods, thanks to @JPLemelin.
- **Small image and quick re-builds**. `COPY` in `package.json` and run `npm install`
**before** `COPY` in your source code. This saves big on build time and keep container lean.
**before** `COPY` in your source code. This saves big on build time and keeps the container image lean.
- **Bind-mount package.json**. This allows adding packages in realtime without rebuilding images. e.g.
`dce node npm install --save <package name>` (dosn't work on all systems)
`docker compose exec -w /opt/node_app node npm install --save <package name>`

## Production-minded Features

- **Use Docker build-in healthchecks**. uses Dockerfile `HEALTHCHECK` with `/healthz` route to
- **Use Docker built-in healthchecks**. This uses Dockerfile `HEALTHCHECK` with `/healthz` route to
help Docker know if your container is running properly (example always returns 200, but you get the idea).
- **Proper NODE_ENV use**. Defaults to `NODE_ENV=production` in Dockerfile and overrides to
`development` in docker-compose for local dev.
- **Don't add dev dependencies into production image**. Proper `NODE_ENV` use means dev dependencies
won't be installed in container by default. Using docker-compose will build with them by default.
- **Enables proper SIGTERM/SIGINT for graceful exit**. Defaults to `node index.js` rather then npm
- **Don't add dev dependencies into the production image**. Proper `NODE_ENV` use means dev dependencies
won't be installed in the image by default. Using Docker Compose will build with them by default.
- **Enables proper SIGTERM/SIGINT for graceful exit**. Defaults to `node index.js` rather than npm
for allowing graceful shutdown of node.
npm doesn't pass SIGTERM/SIGINT properly (you can't ctrl-c when running `docker run` in foreground).
To get `node index.js` to graceful exit, extra signal-catching code is needed.
Expand All @@ -56,8 +56,7 @@ The `Dockerfile` and `index.js` document the options and links to known issues.

## Assumptions

- You have Docker and Docker-Compose installed (Docker for Mac, Docker for Windows,
get.docker.com and manual Compose installed for Linux).
- You have Docker and Docker Compose installed (Docker Desktop for Mac/Windows/Linux).
- You want to use Docker for local development (i.e. never need to install Node.js/npm on host)
and have dev and prod Docker images be as close as possible.
- You don't want to lose fidelity in your dev workflow. You want a easy environment setup,
Expand All @@ -71,32 +70,32 @@ it's meant for happy local development. Use `docker-stack.yml` for Swarm.

If this was your Node.js app, to start local development you would:

- Running `docker-compose up` is all you need. It will:
- Running `docker compose up` is all you need. It will:
- Build custom local image enabled for development (nodemon, `NODE_ENV=development`).
- Start container from that image with ports 80 and 9229 open (on localhost).
- Starts with `nodemon` to restart Node.js on file change in host pwd.
- Mounts the pwd to the app dir in container.
- If you need other services like databases,
just add to compose file and they'll be added to the custom Docker network for this app on `up`.
- Compose should detect if you need to rebuild due to changed package.json or Dockerfile,
but `docker-compose build` works for manually building.
- Be sure to use `docker-compose down` to cleanup after your done dev'ing.
- Compose won't rebuild automatically, so either run `docker compose build` after changing `package.json`
or do what I do and always run `docker compose up --build`.
- Be sure to use `docker compose down` to cleanup after your done dev'ing.

If you wanted to add a package while docker-compose was running your app:

- `docker-compose exec -w /opt/node_app node npm install --save <package name>`
- `docker compose exec -w /opt/node_app node npm install --save <package name>`
- This installs it inside the running container.
- Nodemon will detect the change and restart.
- `--save` will add it to the package.json for next `docker-compose build`
- `--save` will add it to the package.json for next `docker compose build`

To execute the unit-tests, you would:

- Execute `docker-compose exec node npm test`, It will:
- Run a process `npm test` in the container node.
- Execute `docker compose exec node npm test`, It will:
- Run a process `npm test` in the container.
- You can use the *vscode* to debug unit-tests with config `Docker Test (Attach 9230 --inspect)`,
It will:
- Start a debugging process in the container and wait-for-debugger, this is done by *vscode tasks*
- It will also kill previous debugging process if existing.
- It will also kill a previous debugging process if existing.

## Ways to improve security

Expand Down
17 changes: 8 additions & 9 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
version: '2.4'
# version 2.x allows you to use depends_on with conditions that cause
# the node app to wait on mongo to respond to a healthy healthcheck before node is started
# v3.x doesn't have this feature yet, and is only needed if you want to use Swarm
# version: '2.4'
# versions no longer needed for compose CLI use (as of 2020)
# If using swarm, you'll still need version 3.9

services:

Expand All @@ -24,10 +23,10 @@ services:
- .:/opt/node_app/app
# bind-mounting these two files in will let you add packages during development without rebuilding
# for example, to add bower to your app while developing, just install it inside the container
# and then nodemon will restart. Your changes will last until you "docker-compose down" and will
# be saved on host for next build
# NOTE: this won't work on Docker Toolbox (virtualbox) which doesn't bind-mount single files
# docker-compose exec node npm install --save bower
# and then nodemon will restart. Your changes will last until you "docker compose down" and will
# be saved on host for next build.
# remember to isntall from the parent directory to the code bind-mount:
# docker compose exec -w /opt/node_app node npm install --save bower
- ./package.json:/opt/node_app/package.json
- ./package-lock.json:/opt/node_app/package-lock.json
# this is a workaround to prevent host node_modules from accidently getting mounted in container
Expand Down Expand Up @@ -61,7 +60,7 @@ services:
# explains that any data dropped into the docker-entrypoint-initdb.d directory will be injected
# into mongo at startup. This is also a common pattern for other db solutions like mysql or psql
- ./database/seed.js:/docker-entrypoint-initdb.d/seed.js
# we need to check health here, so that docker-compose will wait for a healthy mongo before it starts node
# we need to check health here, so that docker compose will wait for a healthy mongo before it starts node
healthcheck:
test: "[ `echo 'db.runCommand(\"ping\").ok' | mongo localhost/example-database --quiet` ] && echo 0 || echo 1"
interval: 5s
Expand Down
Loading

0 comments on commit 5da9c6c

Please sign in to comment.