Skip to content

Writing Dockerfiles

The drafting of a Dockerfile follows the high-level steps below.

null

Basics

dockerfile
# 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.

dockerfile
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.

dockerfile
RUN apk add --update redis
RUN apk add --update redis

ARG

Specify arguments to be set at the command line

dockerfile
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.

dockerfile
WORKDIR /home/app
WORKDIR /home/app

COPY

Copy files and directories from host . to inside the container /app

dockerfile
COPY . /app
COPY . /app

CMD

Execute a command on container start up.

dockerfile
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.

dockerfile
# 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

dockerfile
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

sh
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.

dockerfile
# 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

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

sh
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

sh
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.

Essential Reading