Usually, Docker images are created using a Dockerfile — a plain text file containing instructions that Docker executes to build an image.
Example:
FROM debian:wheezy
RUN apt-get update && apt-get install -y cowsay
Build the image from the directory containing the Dockerfile:
docker build -t test/cowsay-dockerfile .
Run it like any other image:
docker run test/cowsay-dockerfile /usr/games/cowsay "Moo"
What is a Dockerfile?
Dockerfiles describe how to build an image. Each instruction creates a read-only layer; layers are stacked, and each layer is a delta of changes from the previous one. Images are built with docker image build.
Example:
FROM ubuntu:15.04
COPY . /app
RUN make /app
CMD python /app/app.py
Dockerfile instructions
- FROM — base image for a build stage
- ADD — copy files or fetch remote URLs (prefer COPY for local files)
- ARG — build-time variables
- CMD — default command for a running container
- COPY — copy files from the build context
- ENTRYPOINT — executable invoked when the container starts
- ENV — environment variables inside the image
- EXPOSE — document which ports the container listens on
- LABEL — metadata on the image
- ONBUILD — trigger instructions when used as a parent image
- RUN — execute commands during the build
- USER — user for subsequent instructions
- VOLUME — declare mount points
- WORKDIR — working directory for subsequent instructions
Base images
For a minimal Linux base, consider alpine (~5 MB) with its package manager. For a fuller base with a smaller footprint than ubuntu, debian images share the same package base at a fraction of the size.
To produce the smallest possible image, build from scratch (an empty filesystem) and copy static binaries into it. Binaries must be compiled for the container architecture and include all required libraries (static linking). External command invocation should be avoided.
Rebuilding base images
When you run docker build, Docker reads FROM and pulls the image if it is not present locally. If the image exists locally, Docker uses it without checking for a newer version. Run docker pull on parent images explicitly (or remove them) before rebuilding to ensure you get the latest version — especially important for security updates on widely used bases like debian.
Build context
The build context is the set of local files and directories accessible to ADD and COPY instructions, typically specified as a directory path on the docker build command line. All files under that path are sent to the Docker daemon as part of the build.
If no context is defined — for example, when the Dockerfile is fetched from a URL or piped via STDIN — the build context is empty.
.dockerignore
Use a .dockerignore file to exclude unnecessary files from the build context. One pattern per line; * and ? wildcards are supported.
Example:
# Ignore these files
*/*.md
*/.git
src/docs/
*/tests/
Another common pattern:
.git
*/.git
*/*/.git
*.sw?
Layer caching
Docker caches each layer to speed up subsequent builds. Disable caching with:
docker build --no-cache .
Multi-stage builds
Multi-stage builds let you compile or prepare artifacts in one stage and copy only the result into a smaller final image.
Example:
# Create an image for the weather-app using multi-stage build
FROM node AS build
RUN mkdir -p /var/node/
ADD src/ /var/node/
WORKDIR /var/node
RUN npm install
FROM node:alpine
ARG VERSION=V1.1
LABEL org.label-schema.version=$VERSION
ENV NODE_ENV="production"
COPY --from=build /var/node /var/node
WORKDIR /var/node
EXPOSE 3000
ENTRYPOINT ["./bin/www"]
Build as usual: docker build -t myapp .
User permissions
Always set a non-root user with the USER instruction. Without it, all processes inside the container run as root. Because UIDs are shared between container and host, a container compromise can grant root access on the host.
Example:
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
USER uwsgi
See instruction-user for details.
Best practices
- Keep containers as ephemeral as possible.
- Follow open_in_new Principle 6 of the 12 Factor App.
- Avoid including unnecessary files; use
.dockerignore. - Use multi-stage builds.
- Do not install unnecessary packages.
- Decouple applications.
- Minimize the number of layers.
- Sort multi-line arguments.
- Leverage the build cache.
Naming and tagging images
It is easy to lose track of which source revision matches a given image tag. Use Git commit hashes as tags.
Latest commit hash:
git log -1 --pretty=%H
Build and tag:
docker build -t myapp:$(git log -1 --pretty=%H) .
After building a new version, point latest at it:
docker image tag myapp:<GIT_HASH> myapp:latest
Other ways to create images
- Manual image creation
— snapshot a container with
docker commit - Export and import — flatten or transfer images with tar archives
Andrew Dorokhov