Building Docker Images with Dockerfiles
A Dockerfile serves as a text document containing all the commands required to assemble a Docker image. The docker build command processes these instructions to create an executable image. You can specify a Dockerfile from any location in your filesystem using the -f flag with the docker build command.
Basic Structure of a Dockerfile
A Dockerfile typically consists of four main sections: base image specification, maintainer information, image build instructions, and container startup commands. Comments in Dockerfiles begin with the # character.
Dockerfile Commands
Docker processes instructions in a Dockerfile sequentially from top to bottom. The first instruction must be FROM to specify the base image. Various instructions like RUN, CMD, FROM, EXPOSE, and ENV can be used to construct the image.
FROM: Specifying the Base Image
The FROM instruction initializes a new build stage and sets the base image for subsequent instructions. It must be the first non-comment instruction in the Dockerfile.
FROM <image> FROM <image>:<tag> FROM <image>@<digest>
Example:
FROM ubuntu:20.04
Note: If no tag or digest is specified, the latest tag is used by default.
MAINTAINER: Setting Author Information
The MAINTAINER instruction allows you to set the author field of the generated images.
MAINTAINER <name>
Examples:
MAINTAINER Jane Smith MAINTAINER john.doe@example.com MAINTAINER Jane Smith <john.doe@example.com>
RUN: Executing Commands During Build
The RUN instruction executes any command in a new layer on top of the current image and commits the results.
Two forms are available:
# Shell form RUN <command> # Exec form RUN ["executable", "param1", "param2"]
Examples:
RUN apt-get update && apt-get install -y python3 RUN ["/bin/bash", "-c", "echo hello world"]
Note: Docker caches intermediate layers created by RUN instructions. To disable caching, use the --no-cache flag with docker build.
ADD: Copying Files and URLs
The ADD instruction copies new files, directories, or remote file URLs from <src> and adds them to the filesystem of the container at the path <dest>.
ADD <src>... <dest> ADD ["<src>",... "<dest>"] # For paths containing spaces
Examples:
ADD hom* /mydir/ # Add all files starting with "hom" ADD hom?.txt /mydir/ # Add files like "home.txt" (? matches single character) ADD test relativeDir/ # Add "test" to WORKDIR/relativeDir/ ADD http://example.com/file /data/ # Download and add a file
Note: ADD automatically extracts compressed files (except remote URLs) from the local build context.
COPY: Copying Files
The COPY instruction copies new files or directories from <src> and adds them to the filesystem of the container at the path <dest>. Similar to ADD, but with less functionality.
COPY <src>... <dest> COPY ["<src>",... "<dest>"] # For paths containing spaces
Examples:
COPY src/ /usr/src/app/ COPY ["config file", /etc/config/]
CMD: Setting Default Commands
The CMD instruction sets a default command that will be executed only when you run the container without specifying a command.
Three forms are available:
# Exec form (preferred) CMD ["executable", "param1", "param2"] # Providing default parameters for ENTRYPOINT CMD ["param1", "param2"] # Shell form CMD command param1 param2
Examples:
CMD ["nginx", "-g", "daemon off;"] CMD echo "This is a test." CMD ["/usr/bin/wc", "--help"]
Note: CMD is different from RUN - CMD specifies the command to run when starting a container, while RUN specifies commands during image building. Only the last CMD instruction in a Dockerfile will be used.
ENTRYPOINT: Configuring Default Executable
The ENTRYPOINT instruction allows you to configure a container that will run as an executable.
Two forms are available:
# Exec form (preferred) ENTRYPOINT ["executable", "param1", "param2"] # Shell form ENTRYPOINT command param1 param2
Examples:
ENTRYPOINT ["top", "-b"] ENTRYPOINT /bin/sh -c "echo hello"
Note: ENTRYPOINT is similar to CMD but differs in that command line arguments to docker run will be appended to the ENTRYPOINT, not override it. Only the last ENTRYPOINT instruction in a Dockerfile will be used.
Combining CMD and ENTRYPOINT
When used together, CMD provides default parameters for the ENTRYPOINT instruction:
ENTRYPOINT ["executable", "param1"] CMD ["param2"]
Running docker run image will execute: executable param1 param2
Running docker run image custom will execute: executable param1 custom
LABEL: Adding Metadata
The LABEL instruction adds metadata to an image in the form of key-value pairs.
LABEL <key>=<value> <key>=<value> ...
Examples:
LABEL version="1.0" description="Web server" vendor="Example Corp" LABEL maintainer="John Doe <john.doe@example.com>"
Note: Using a single LABEL instruction with multiple labels is recommended to reduce the number of intermediate layers.
ENV: Setting Environment Variables
The ENV instruction sets environment variables in the container.
Two forms are available:
ENV <key> <value> # Only sets one variable ENV <key>=<value> ... # Sets multiple variables
Examples:
ENV APP_VERSION=1.3.7 ENV NODE_ENV=production ENV DEBIAN_FRONTEND=noninteractive
EXPOSE: Declaring Ports
The EXPOSE instruction informs Docker that the container listens on specified network ports at runtime.
EXPOSE <port> [<port>/<protocol>...]
Examples:
EXPOSE 80 EXPOSE 80/tcp 443/tcp EXPOSE 53/udp
Note: EXPOSE doesn't actually publish the port. It's documentation for the person running the container. To publish the port, use the -p flag with docker run or the -P flag to publish all exposed ports.
VOLUME: Creating Mount Points
The VOLUME instruction creates a mount point with the specified name and marks it as holding externally mounted volumes from native host or other containers.
VOLUME ["/path/to/dir"]
Examples:
VOLUME ["/data"] VOLUME ["/var/www", "/var/log/apache2"]
Features of volumes:
- Can be shared and reused between containers
- Changes to volumes are immediate
- Changes to volumes don't affect the base image
- Exist until no containers use them
WORKDIR: Setting Working Directory
The WORKDIR instruction sets the working directory for any subsequent instructions like RUN, CMD, ENTRYPOINT, COPY, and ADD.
WORKDIR /path/to/workdir
Examples:
WORKDIR /app WORKDIR /app/backend WORKDIR /app/backend/api
Note: If the WORKDIR doesn't exist, it will be created automatically. The working directory can be overridden with the -w flag when running a container.
USER: Setting User
The USER instruction sets the user name or UID to use when running the image and for any subsequent RUN instructions.
USER <username>[:<group>] USER <UID>[:<GID>]
Examples:
USER www-data USER user1:group1 USER 1001
Note: The user must be created in the image with the RUN instruction or in the base image first. The user can be overridden with the -u flag when running a container.
ARG: Defining Build Variables
The ARG instruction defines a variable that users can pass at build time to the builder using the --build-arg flag.
ARG <name>[=<default value>]
Examples:
ARG BUILD_DATE ARG VERSION=1.0 ARG GIT_COMMIT=unknown
ONBUILD: Setting Build Triggers
The ONBUILD instruction adds a trigger instruction to the image that will be executed later when the image is used as a base for another build.
ONBUILD [INSTRUCTION]
Examples:
ONBUILD ADD . /app/src ONBUILD RUN npm install ONBUILD RUN chown -R www-data:www-data /app
Example Dockerfile
Here's an example Dockerfile for creating a Python application:
# Python application Dockerfile
# Version 2.0
# Base image
FROM python:3.9-slim
# Set maintainer
LABEL maintainer="Jane Doe <jane.doe@example.com>"
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Create app directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create non-root user
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
# Expose port
EXPOSE 8000
# Start command
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:application"]
Understanding CMD and ENTRYPOINT Interaction
Both CMD and ENTRYPOINT define what command gets executed when running a container. However, they work differently together:
- CMD: Provides default arguments for the executing command. These can be overridden from the command line when running the container.
- ENTRYPOINT: Configures a container that will run as an executable. Arguments from the command line are appended to the ENTRYPOINT command.
When used together, CMD provides default arguments to ENTRYPOINT:
ENTRYPOINT ["executable", "param1"] CMD ["param2"]
Behavior:
- docker run image: executes executable param1 param2
- docker run image custom: executes executable param1 custom
- docker run image --flag: executes executable param1 --flag
This pattern allows you to create a container that always runs a specific executable (via ENTRYPOINT) while allowing users to customize its behavior via command line arguments (which get passed to CMD).