Creating Efficient Docker Images with Dockerfile

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 --file or -f flag
  • 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 time
  • RUN <command> - Creates a new layer on top of the current state and executes commands, supporting shell form for command execution
  • WORKDIR <directory> - Sets the working directory for subsequent instructions
  • COPY <src> <dest> - Copies files from the host machine into the image
  • CMD <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 AS keyword 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 .dockerignore file
  • 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

Tags: docker dockerfile containerization image-optimization multi-stage-builds

Posted on Fri, 11 Sep 2026 16:36:57 +0000 by dank