Writing Dockerfiles
The drafting of a Dockerfile follows the high-level steps below.
Basics
# You *must* start by specifying your image and optionally your build stage
FROM ubuntu:20.04 AS build
# Set your working directory and then copy all the contents from the
# project into the container's filesystem
WORKDIR /home/app
COPY . .
# You can run any old bash command to setup the image
RUN <any old bash command>
RUN apt update && apt install -y curl git
# Specify arguments that may be passed on the command line at build time
ARG [email protected]
# You *must* start by specifying your image and optionally your build stage
FROM ubuntu:20.04 AS build
# Set your working directory and then copy all the contents from the
# project into the container's filesystem
WORKDIR /home/app
COPY . .
# You can run any old bash command to setup the image
RUN <any old bash command>
RUN apt update && apt install -y curl git
# Specify arguments that may be passed on the command line at build time
ARG [email protected]
Important Keywords
FROM <image> AS <stage>
Specifies the image and target build layer.
FROM ubuntu:20.04 AS build
...
FROM ubuntu:20.04 AS deploy
COPY --from=build /app/your_cache your_cache
FROM ubuntu:20.04 AS build
...
FROM ubuntu:20.04 AS deploy
COPY --from=build /app/your_cache your_cache
Tip
When creating images for production environments, use multi-step build process in your Docker Image.
RUN <some command>
Run an arbitrary command. Useful for installing dependencies and setting up configurations.
RUN apk add --update redis
RUN apk add --update redis
ARG
Specify arguments to be set at the command line
ARG SSH_KEY_PRIVATE
ARG SSH_KEY_PRIVATE
WORKDIR
Specify the working directory. It is bad practice to use the root folder /
as the working directory.
WORKDIR /home/app
WORKDIR /home/app
COPY
Copy files and directories from host .
to inside the container /app
COPY . /app
COPY . /app
CMD
Execute a command on container start up.
CMD ["pnpm", "run", "develop"]
CMD ["pnpm", "run", "develop"]
Concepts
Multi-stage Build
By specifying the <stage>
you can enable simple multi-stage builds, which allows you to copy stage-specific artifacts, reducing the final size of the build image.
# Example 1: Two Ubuntu images
FROM ubuntu:20.04 AS build
...
FROM ubuntu:20.04 AS deploy
COPY --from=build /app/your_cache your_cache
# Example 2: Alpine and Nginx
FROM node:alpine AS builder
...
FROM nginx
COPY --from=builder /app/build /usr/share/nginx/html
# Example 1: Two Ubuntu images
FROM ubuntu:20.04 AS build
...
FROM ubuntu:20.04 AS deploy
COPY --from=build /app/your_cache your_cache
# Example 2: Alpine and Nginx
FROM node:alpine AS builder
...
FROM nginx
COPY --from=builder /app/build /usr/share/nginx/html
Tip
When creating images for production environments, use multi-step build process in your Docker Image.
Tips and Tricks
Using secrets when cloning
RUN --mount=type=secret,id=glpat,target=/root/glpat \
git clone \
https://USERNAME:$(cat /root/glpat)@gitlab.com/group/project/custom.git
RUN --mount=type=secret,id=glpat,target=/root/glpat \
git clone \
https://USERNAME:$(cat /root/glpat)@gitlab.com/group/project/custom.git
Command to build
docker buildx build --secret id=glpat,src=/tmp/glpat --tag IMAGE:latest .
docker buildx build --secret id=glpat,src=/tmp/glpat --tag IMAGE:latest .
Avoid unnecessary rebuild
A good practice is to breakout the COPY
commands in your Dockerfile only for files required for subsequent RUN
commands. This way, only when critical files are changed does the build process start from the beggining. Recall that Docker will run any steps after the step for which is recognized a change.
# npm install only needs `package.json`, if you change any
# source files, npm install won't be re-run
COPY ./package.json ./
RUN npm install
COPY ./ ./
# npm install only needs `package.json`, if you change any
# source files, npm install won't be re-run
COPY ./package.json ./
RUN npm install
COPY ./ ./
Using SSH
This method is not recommended since it places the private key in the image. Instead it is preferred to use the method above for passing in secrets.
In your Dockerfile
RUN apt-get update && apt-get -y install git
ARG SSH_PRIVATE_KEY
RUN mkdir /root/.ssh/ \
&& echo "${SSH_PRIVATE_KEY}" > /root/.ssh/id_rsa \
&& chmod 600 /root/.ssh/id_rsa \
&& touch /root/.ssh/known_hosts \
&& ssh-keyscan website.com >> /root/.ssh/known_hosts
RUN apt-get update && apt-get -y install git
ARG SSH_PRIVATE_KEY
RUN mkdir /root/.ssh/ \
&& echo "${SSH_PRIVATE_KEY}" > /root/.ssh/id_rsa \
&& chmod 600 /root/.ssh/id_rsa \
&& touch /root/.ssh/known_hosts \
&& ssh-keyscan website.com >> /root/.ssh/known_hosts
Command to build
docker build --build-arg SSH_PRIVATE_KEY="$(cat ~/.ssh/id_ed25519)" -t IMAGE:latest --file Dockerfile .
docker build --build-arg SSH_PRIVATE_KEY="$(cat ~/.ssh/id_ed25519)" -t IMAGE:latest --file Dockerfile .
or using buildx
docker buildx build --build-arg SSH_PRIVATE_KEY="$(cat ~/.ssh/id_ed25519)"
docker buildx build --build-arg SSH_PRIVATE_KEY="$(cat ~/.ssh/id_ed25519)"
The buildx project extends the standard build options available with Docker.