Last active
May 29, 2023 19:10
-
-
Save Popsiclestick/666be072aa3eeda4f8f9ed6a7b31e283 to your computer and use it in GitHub Desktop.
Dockerfile example with best practices
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### Generic Dockerfile demonstrating good practices | |
### Imports | |
# Bad. You risk both the stability and security of your application | |
# You don't know what they might merge into their image or who they may give control of the project | |
# https://twitter.com/b0rk/status/1226856930875932672/photo/1 | |
FROM random-person/golang:latest | |
# Bad-ish. We don't need Ubuntu, it comes with unnecessary bloat | |
# Nor do we want latest. Predictibility is better, especially when using in a build system, etc | |
FROM ubuntu:latest | |
# Good. Using a small image since our app has no dependency on Ubuntu | |
FROM alpine:3.11 | |
# Good. Use an image that is specifically built for the needed dependencies | |
FROM golang:1.13-alpine3.11 | |
# Sometimes it is better to build your own image from scratch | |
# You can build slim containers with exactly what you need. Default packages are typically large and increase attack surface | |
### Installing packages | |
# Bad. Every command causes the previous image to change, thus creating a new layer | |
# This also leaves package cache files that will persist in the image | |
# Install in one command unless there is a logical separation between commands | |
RUN apk update | |
RUN apk add wget | |
RUN apk add git | |
# Better. Lets update, add, and clean up on one line. Also use --update instead of using a separate build commit | |
RUN apk add --update wget git \ | |
&& rm -rf /var/cache/apk/* | |
# Best. Avoid caching content when possible | |
RUN apk add --no-cache wget git | |
### Users & Groups | |
# If a service can run without privileges, use USER to change to a non-root user. | |
# Start by creating the user and group. We do not want to be running as the root user. | |
# The goal is to setup our users applications, drop our permissions, and run our application | |
RUN adduser -D -u 1000 -g 1000 container-user | |
### Fetching files | |
# Bad. We do not want to keep temporary or unused file between commands, always clean up in the same command | |
RUN wget http://example.com/my/large/app.tar.gz -O /root/app.tar.gz | |
RUN mkdir /opt/somedir | |
RUN mv /root/app.tar.gz /opt/somedir/app.tar.gz | |
RUN tar -zxvf /opt/somedir/app.tar.gz | |
# Good. Doing this as a one liner and piping to tar leaves nothing on the server but the extracted files | |
# Note: If you don't switch to your container user, you'll need to remember to `chown -R` your files later | |
USER container-user | |
RUN mkdir /opt/somedir \ | |
&& wget -q -O- http://example.com/my/large/app.tar.gz \ | |
| tar -zxv -C /opt/somedir/ | |
### Config files | |
# Non-ideal. Don't bake configuration into an image as a general rule of thumb, especially with sensitive data | |
COPY super_secret_database.conf /opt/app/config/database.yml | |
# Better. Write a wrapper script that will take arguments | |
COPY entrypoint.sh | |
ENTRYPOINT ["/entrypoint.sh"] # where entrypoint takes config information and passes to the app or updates the config | |
# Better. Utilize environment variables. Update database.yml to read from ENV and pass them when running | |
# docker run -e PROD_DB=localhost myapp | |
# Best. Utilize your orchestrator to mount sensitive config files and secrets on volumes backed by tmpfs (Memory filesystem). | |
# Kubernetes, Swarm, and Nomad support memory based filesystem mounts | |
# Note: Try to avoid passing secrets in environment variables. Application misconfigurations could leak secrets. | |
### Drop user privileges | |
# Optimal. If you didn't already drop privileges. You want to do this before ENTRYPOINT/CMD options | |
# We want to drop privileges down to our application user to avoid running as root | |
USER 1000 | |
### Allow default arguments with CMD | |
# Bad. Leaves little to no flexibility | |
ENTRYPOINT ["/entrypoint.sh", "--db localhost"] | |
# Good. ENTRYPOINT acts as our image's main command, CMD allows us to set default flags. | |
# This provides us a way to override those agruments "docker run myapp --db 192.0.2.10" | |
ENTRYPOINT ["/entrypoint.sh"] | |
CMD ["--db", "localhost"] | |
# Note: The main purpose of a CMD is to provide defaults for an executing container. | |
# These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINT instruction as well. | |
### Run applications in the foreground - Do not daemonize applications | |
# Bad. NGINX runs as a daemon by default | |
ENTRYPOINT nginx | |
# Good. Capture the output to stdout/stderr and keep Nginx in the foreground | |
RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log | |
CMD ["nginx", "-g", "daemon off;"] | |
# Better. Run your application with a lightweight init wrapper like Tini (https://github.com/krallin/tini) | |
# Tini provides some added benefit by handling things like zombie processes and signals | |
ENTRYPOINT ["/tini", "--"] | |
CMD ["/your/program", "-and", "-its", "arguments"] | |
# Note: If you're running Nginx, I'd recommend looking at something like https://hub.docker.com/r/nginxinc/nginx-unprivileged | |
### Only run one application per container | |
# Bad. Running rails and then running NGINX in front of it | |
RUN apk add --update nginx ruby | |
RUN /opt/rails/bin/rails s | |
CMD ["nginx", "-g", "daemon off;"] | |
# Good. Run two separate containers for each application | |
# They can resolve and communicate through the overlay network | |
# docker network create --driver bridge mynetwork | |
# docker run --name myrailsapp --net mynetwork rails | |
# docker run --name nginxfrontend --net mynetwork -e RAILS_HOST=myrailsapp nginx | |
# Note: This is where orchestrators like Swarm, Kubernetes, and Nomad come into play | |
# Find more examples: | |
# https://github.com/Popsiclestick/container-best-practices |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment