Dockerfile
What is a Dockerfile?
A Dockerfile is a plain text file that contains a set of instructions used to build a Docker image. It defines everything needed for the environment inside the container, including:
- Base operating system
- Application code
- Dependencies
- Configurations
- Runtime instructions
Docker reads the Dockerfile line by line and executes the commands to produce an image.
Why Use a Dockerfile?
- Automation: Repeatable builds without manual setup.
- Consistency: Same environment across development, testing, and production.
- Portability: Run your application anywhere Docker is available.
- Versioning: Docker images can be tagged and rolled back.
Common Dockerfile Instructions
| Instruction | Description | Example |
|---|---|---|
FROM |
Defines the base image | FROM ubuntu:20.04 |
WORKDIR |
Sets the working directory inside the container | WORKDIR /app |
COPY |
Copies files from host into container | COPY . /app |
ADD |
Similar to COPY but can also handle URLs and archives |
ADD https://example.com/app.tar.gz /app/ |
RUN |
Executes a command during build | RUN apt-get update && apt-get install -y curl |
CMD |
Default command to run at container start | CMD ["node", "server.js"] |
ENTRYPOINT |
Defines a fixed executable | ENTRYPOINT ["python3"] |
EXPOSE |
Documents the port the container listens on | EXPOSE 8080 |
ENV |
Sets environment variables | ENV NODE_ENV=production |
ARG |
Defines build-time variables | ARG VERSION=1.0.0 |
VOLUME |
Declares mount points for persistence | VOLUME /data |
Example Dockerfile
# Use an official Node.js image as the base
FROM node:18
# Set working directory
WORKDIR /usr/src/app
# Copy package files and install dependencies
COPY package*.json ./
RUN npm install
# Copy application source code
COPY . .
# Expose application port
EXPOSE 3000
# Default command
CMD ["npm", "start"]
Explanation
FROM node:18: Start from Node.js 18 image.WORKDIR /usr/src/app: Set working directory inside container.COPY package*.json ./: Copy package files first (to use Docker layer caching).RUN npm install: Install dependencies.COPY . .: Copy all application files.EXPOSE 3000: Declare that the app listens on port 3000.CMD ["npm", "start"]: Define the container’s startup command.
ADD vs COPY
- COPY: Use for copying local files and directories.
- ADD: Can also handle remote URLs and compressed archives.
Best practice: UseCOPYunless you specifically needADDfeatures.
CMD vs ENTRYPOINT
- CMD: Provides default arguments for the container. Can be overridden at runtime.
- ENTRYPOINT: Defines the main executable that cannot be easily overridden.
Example:
ENTRYPOINT ["python"]
CMD ["app.py"]
Running docker run myimage → executes python app.py.
Running docker run myimage other.py → executes python other.py.
Exec Form vs Shell Form
- Exec form (recommended):
CMD ["nginx", "-g", "daemon off;"]
Runs directly, avoids an extra shell process.
- Shell form:
CMD nginx -g "daemon off;"
Runs in /bin/sh -c, allows shell features like variables but adds overhead.
Build Arguments (ARG)
ARG allows you to pass variables at build time:
ARG VERSION=1.0
RUN echo "Building version $VERSION"
Build with:
docker build --build-arg VERSION=2.0 -t myapp:2.0 .
Building and Running a Docker Image
- Build the image
docker build -t myapp:1.0 .
- Run a container
docker run -d -p 3000:3000 myapp:1.0
Updating and Rebuilding an Image
When you make changes (e.g., update source code or dependencies):
- Modify files in your project.
FROM olduser/oldrepo:version
RUN echo "Added extra changes." > /info.txt
- Rebuild the image with a new tag:
docker build -t newuser/newrepo:version .
- View your changes (info.txt) in the new image:
docker run -it newuser/newrepo:version ls -al /
- Run the updated container:
docker run -d -p 3000:3000 newuser/newrepo:version
- Push to a registry (if needed):
docker push newuser/newrepo:version
Inspecting Docker Images
1. Check Dockerfile inside an Image
Images do not always include their original Dockerfile. However, you can reconstruct information using:
- docker history
docker history myapp:1.0
- docker inspect
docker inspect myapp:1.0
docker image inspect myapp:1.0
- Dive (recommended tool)
Dive is a CLI tool that shows layer contents and Dockerfile-like instructions.
Installation (if not already installed):
brew install dive
dive --version # Verify installation
dive myapp:1.0
2. Docker Hub
If the image is published on Docker Hub, the repository may include the original Dockerfile. Check under the " Dockerfile" or "Dockerfile (view on GitHub)" section.
Multi-Stage Builds
Multi-stage builds help create smaller and more secure production images.
You can use one stage for building and another for the final runtime:
# Stage 1: Build
FROM golang:1.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Stage 2: Production
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]
This way, the final image contains only the binary and no build dependencies.
Best Practices
- Use a small base image (e.g.,
alpine) to reduce size. - Use multi-stage builds for production images.
- Leverage Docker layer caching by ordering instructions wisely.
- Keep images clean by removing unnecessary files.
- Always pin versions to avoid unexpected changes.