Docker Images
Docker images are the building blocks of containers. They are read-only templates containing:
- Application code
- Runtime environment
- Dependencies
- Configuration
Docker images function like a complete filesystem snapshot that provides everything needed to run an application. They are:
- Immutable: Once built, the content doesn't change
- Layered: Composed of multiple filesystem layers for efficiency
- Cacheable: Layers can be reused across multiple images
- Distributable: Can be shared via registries
- Versioned: Tagged with version information for tracking changes
Each image is identified by a unique SHA256 digest and can have multiple human-readable tags (like nginx:latest
or ubuntu:20.04
). The image's architecture determines what CPU architectures it can run on (x86_64, ARM64, etc.).
Working with Dockerfiles
A Dockerfile is a text document containing all commands to build an image. It provides a declarative, reproducible way to define your container environment:
Common Dockerfile Instructions
Instruction | Purpose | Example |
---|---|---|
FROM | Specifies the base image | FROM ubuntu:20.04 |
WORKDIR | Sets the working directory | WORKDIR /app |
COPY | Copies files from host to image | COPY . /app |
ADD | Copies files with extra features (URL support, tar extraction) | ADD https://example.com/file.tar.gz /app |
RUN | Executes commands in a new layer | RUN apt-get update && apt-get install -y curl |
ENV | Sets environment variables | ENV NODE_ENV=production |
EXPOSE | Documents which ports are intended to be published | EXPOSE 8080 |
CMD | Provides default command to run | CMD ["node", "app.js"] |
ENTRYPOINT | Configures container to run as executable | ENTRYPOINT ["nginx", "-g", "daemon off;"] |
VOLUME | Creates a mount point | VOLUME /data |
USER | Sets the user for subsequent instructions | USER node |
ARG | Defines build-time variables | ARG VERSION=latest |
Each instruction in a Dockerfile creates a new layer in the image, which affects both the build time and the final image size.
Building Images
Basic Build Command
The -t
flag tags the image with a name and version. The .
specifies the build context (the directory containing your Dockerfile and application files). When you run this command, Docker:
- Sends the build context to the Docker daemon
- Executes each instruction in the Dockerfile sequentially
- Creates intermediate containers for each step
- Commits each step as a new image layer
- Removes intermediate containers
- Tags the final image with the specified name
Build with Different Dockerfile
The -f
flag allows you to specify a custom Dockerfile name. This is useful when maintaining multiple Dockerfiles for different environments (development, testing, production) or platforms.
Build with Build Arguments
Build arguments allow you to pass variables to the build process, which can be used in your Dockerfile with the ARG
instruction. This enables parameterized builds that can produce different images based on the provided arguments.
Advanced Build Options
Image Management
Image Naming Conventions
Docker images follow a specific naming convention:
- Registry domain: (Optional) Defaults to Docker Hub if not specified
- Repository name: The name of the image
- Tag: (Optional) Defaults to "latest" if not specified
- Digest: (Optional) SHA256 hash that uniquely identifies the image
Examples:
nginx:1.21.0
(Docker Hub, nginx repository, tag 1.21.0)gcr.io/project/image:v1
(Google Container Registry, project/image repository, tag v1)localhost:5000/myapp:dev
(Local registry on port 5000, myapp repository, tag dev)ubuntu@sha256:a56414c82641178a5628fc1a4d281cd39e4fb0f1b94455c693dbc4ae0a7c310e
(Image by digest)
Multi-stage Builds
Multi-stage builds help create smaller production images by separating the build environment from the runtime environment:
Benefits of Multi-stage Builds
- Smaller final images: Only include what's needed for runtime
- Better security posture: Fewer dependencies mean smaller attack surface
- Separation of concerns: Build tools don't contaminate production image
- Improved performance: Smaller images load faster and use less bandwidth
- Reduced complexity: Single Dockerfile instead of multiple build scripts
Advanced Multi-stage Techniques
You can target specific stages during build with --target
:
Best Practices
- Use specific base image tags
- Avoid
latest
tag in production for reproducibility - Pin to specific versions like
python:3.9.16-slim
rather thanpython:3.9-slim
- Consider using image digests for immutability:
python@sha256:a3edbb33253c7e96d3c8e40d52ef6cde268e3a6041516033c11468a7ff411870
- Balance specificity with maintenance burden
- Avoid
- Minimize layer count
- Combine related RUN commands with
&&
and\
for line continuation - Clean up in the same layer where installations happen
- Example:
RUN apt-get update && apt-get install -y package && apt-get clean && rm -rf /var/lib/apt/lists/*
- Remember that each instruction creates a new layer
- Combine related RUN commands with
- Use .dockerignore
- Exclude unnecessary files from the build context
- Speeds up the build process by reducing context size
- Prevents accidental inclusion of sensitive files
- Pattern syntax similar to .gitignore
- Example contents:
.git
,node_modules
,*.log
,.env*
,tests/
- Order commands by change frequency
- Put instructions that change less frequently at the top
- Install dependencies before copying application code
- Leverage build cache effectively
- Example order: FROM → ENV/ARG → RUN (dependencies) → COPY (config) → COPY (code) → CMD
- Use multi-stage builds for production
- Separate build-time dependencies from runtime dependencies
- Copy only necessary artifacts from build stage
- Dramatically reduce final image size
- Improve security by reducing attack surface
- Consider different stages for testing, building, and production
- Additional best practices
- Run containers with non-root users when possible
- Set appropriate environment variables
- Use health checks for container status monitoring
- Consider distroless or minimal base images
- Scan images for vulnerabilities before deployment