Understanding Dockerfile Basics
Docker builds images by reading instructions from a Dockerfile, similar to how Make uses Makefiles to compile projects in C development.
- A Dockerfile contains specific formatting instructions that Docker follows to assemble application images. The official documentation is available at docs.docker.com
- Commands in Dockerfiles are case-insensitive, and comments are added using the
#symbol - The default filename for Dockerfile is simply "Dockerfile" without any extension. If using a different name or multiple files, specify with the
--fileor-fflag - Docker images consist of read-only layers, with each layer corresponding to an instruction in the Dockerfile
Essential Dockerfile Commands
FROM <image>- Similar to import statements in programming languages, this command uses an existing image as a base, saving build timeRUN <command>- Creates a new layer on top of the current state and executes commands, supporting shell form for command executionWORKDIR <directory>- Sets the working directory for subsequent instructionsCOPY <src> <dest>- Copies files from the host machine into the imageCMD <command>- Defines the default program to run when a container is started from the image. Only the last CMD insrtuction takes effect if multiple are present
Practical Example: Building a Flask Application
Here's a Dockerfile that creates a Flask service based on Ubuntu 22.04. The line # syntax=docker/dockerfile:1 declares the Dockerfile syntax version being used, similar to how shell scripts begin with #!/bin/bash.
# syntax=docker/dockerfile:1
# Use Ubuntu 22.04 as the base image
FROM ubuntu:22.04
# Update package lists and install required packages
RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip install -i https://mirrors.ustc.edu.cn/pypi/web/simple pip -U && pip install flask==2.1.* -i https://mirrors.ustc.edu.cn/pypi/web/simple
# Copy the application file into the container
COPY app.py /
# Set environment variable for Flask
ENV FLASK_APP=app
# Expose port 8000 for the application
EXPOSE 8000
# Command to run when the container starts
CMD flask run --host 0.0.0.0 --port 8000
For the Flask application file (app.py), you can create your own implementation or use the following example:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Welcome to Docker Containerized Flask App!"
To build the image, execute docker build -t my-flask-app:latest .. If your Dockerfile has a different name, use the -f flag to specify it. The build process might take some time.
The "latest" tag can be customized as needed. To test the built image, create a container using docker run -p 8000:8000 my-flask-app:latest.
user@ubuntu:~/projects/flask-docker$ docker run -p 8000:8000 my-flask-app:latest
- Serving Flask app 'app' (lazy loading)
- Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
- Running on all addresses (0.0.0.0)
- Running on http://127.0.0.1:8000
- Running on http://172.17.0.2:8000 Press CTRL+C to quit 172.17.0.1 - - [16/Apr/2023 01:37:25] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [16/Apr/2023 01:37:25] "GET /favicon.ico HTTP/1.1" 404 -
<p>To create a base image from scratch, use <code>FROM scratch</code>. For methods on packaging Linux distributions, refer to the Docker GitHub repository.</p>
<h2>Optimizing Image Builds</h2>
<p>One of the most challenging aspects of building images is managing their size. Each RUN, COPY, and ADD instruction in a Dockerfile adds a new layer to the image.</p>
- Remember to clean up unnecessary files before moving to the next layer
- Use shell techniques and other methods to keep layers as small as possible
It's important to note that optimization is primarily for production builds, not necessarily for development environments.
Multi-Stage Builds
Multi-stage builds help reduce image size by allowing you to use multiple FROM statements in a single Dockerfile:
# syntax=docker/dockerfile:1
# First stage: build the application
FROM golang:1.18 AS build-env
WORKDIR /app
RUN go get -d -v golang.org/x/net/html
COPY main.go ./
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o webserver .
# Second stage: create the final image
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app/
COPY --from=build-env /app/webserver ./
CMD ["./webserver"]
- Multi-stage builds avoid creating intermediate images. If the above example used two separate Dockerfiles, an intermediate image would exist
- The
ASkeyword assigns an alias to the build stage, not to the image itself - To build up to a specific stage, use its alias:
docker build --target build-env -t myapp:build .
Cache Management
In Docker's layer-based build system, changes to one layer affect all subsequent layers.
- To accelerate image builds, minimize the content in each build. Place frequently changing files in separate COPY instructions
- Exclude unnecessary files using a
.dockerignorefile - Package manager caches should be managed carefully. If needed, use the dedicated cache for RUN instructions. For detailde operations, refer to Docker's cache backend documentation
- Choose appropriate base images and minimize layers by reducing RUN, COPY, and ADD instructions. Combine multiple RUN commands when possible, creating readable multi-line scripts