← Back
后端开发 2026.03.07

Go Engineering System Course 015

后端开发

I. Docker Core Concepts

1.1 What is Docker

Docker is an open-source containerization platform that packages applications and all their dependencies into a standardized unit (container), achieving “build once, run anywhere.” For Go developers, Docker addresses the following pain points:

  • Inconsistent development and production environments
  • Complex dependency management (databases, caches, message queues, and other middleware)
  • Non-standardized deployment processes
  • Difficulty in multi-service collaboration in a microservices architecture

1.2 Three Core Concepts

Image

An image is a read-only template that contains all the file system layers required to run an application. It can be compared to a “Class”:

  • An image consists of multiple layers, each representing an instruction in the Dockerfile
  • Layers are read-only and reusable; multiple images can share underlying layers
  • Images are version-managed using tag, e.g., golang:1.22-alpine
+---------------------------+
|   应用代码层 (COPY .)      |  <-- 最上层,变化最频繁
+---------------------------+
|   依赖安装层 (go mod)      |
+---------------------------+
|   基础工具层 (RUN apk)     |
+---------------------------+
|   基础镜像层 (alpine)      |  <-- 最底层,最稳定
+---------------------------+

Container

A container is a running instance of an image, comparable to an “Object”:

  • A container adds a writable layer on top of an image
  • Each container has its own independent file system, network, and process space
  • Containers are temporary and stateless; data in the writable layer is lost upon destruction
  • Data requiring persistence should use data volumes (Volume)

Registry

A registry is a service for storing and distributing images:

  • Docker Hub: The official public registry, similar to GitHub
  • Private Registries: Such as Harbor, AWS ECR, Alibaba Cloud ACR
  • Images are identified by the format registry/repository:tag, e.g., docker.io/library/golang:1.22

II. Writing a Dockerfile (Go Project Example)

2.1 Basic Dockerfile

Suppose we have a simple Go Web service:

// main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        fmt.Fprintf(w, "OK")
    })

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello from Go Docker Service!")
    })

    log.Printf("Server starting on port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

The simplest but not recommended Dockerfile:

# 不推荐:镜像体积大,包含编译工具链
FROM golang:1.22

WORKDIR /app
COPY . .
RUN go build -o server .

EXPOSE 8080
CMD ["./server"]

Images generated this way typically exceed 800MB because they include the complete Go toolchain.

2.2 Multi-stage Build

Multi-stage builds are a core technique for Dockerizing Go projects, capable of reducing image size from 800MB+ to 10-20MB:

# ============ 第一阶段:编译 ============
FROM golang:1.22-alpine AS builder

# 设置 Go 模块代理(国内加速)
ENV GOPROXY=https://goproxy.cn,direct

WORKDIR /app

# 先复制依赖文件,利用缓存层(详见 2.3 节)
COPY go.mod go.sum ./
RUN go mod download

# 复制源代码并编译
COPY . .

# CGO_ENABLED=0 生成静态链接的二进制文件
# -ldflags="-s -w" 去除调试信息,减小体积
RUN CGO_ENABLED=0 GOOS=linux go build \
    -ldflags="-s -w" \
    -o /app/server .

# ============ 第二阶段:运行 ============
FROM alpine:3.19

# 安装必要的 CA 证书(HTTPS 请求需要)和时区数据
RUN apk --no-cache add ca-certificates tzdata

# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# 从 builder 阶段复制编译好的二进制文件
COPY --from=builder /app/server .

# 复制配置文件(如果有)
# COPY --from=builder /app/config ./config

# 切换到非 root 用户
USER appuser

EXPOSE 8080

# 使用 ENTRYPOINT 而非 CMD,防止被意外覆盖
ENTRYPOINT ["./server"]

More extreme solution — using a scratch empty image:

# 编译阶段同上...

# 使用空镜像,最终镜像仅包含二进制文件
FROM scratch

# 从 builder 复制 CA 证书
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# 从 builder 复制时区信息
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

COPY --from=builder /app/server /server

EXPOSE 8080
ENTRYPOINT ["/server"]

Image size comparison:

Base ImageFinal SizeUse Case
golang:1.22~850MBNot recommended for production
alpine:3.19~15MBRecommended, supports shell debugging
scratch~8MBExtremely minimal, no shell
distroless~12MBGoogle recommended, good security

2.3 Build Cache Optimization

During Docker builds, each layer is cached. As long as the input for a layer hasn’t changed, the cache will be reused. The key principle is: place layers with low change frequency first, and layers with high change frequency later.

# 好的做法:分离依赖下载和代码编译
COPY go.mod go.sum ./       # 依赖文件变化少,缓存命中率高
RUN go mod download          # 只有依赖变化时才重新下载
COPY . .                     # 代码频繁变化
RUN go build -o server .     # 每次代码变化都重新编译

# 坏的做法:一次性复制所有文件
COPY . .                     # 任何文件变化都导致后续所有层缓存失效
RUN go mod download
RUN go build -o server .

2.4 Best Practices Summary

.dockerignore File

Create a .dockerignore file in the project root to reduce build context size:

# .dockerignore
.git
.gitignore
.idea
.vscode
*.md
README*
LICENSE
Makefile
docker-compose*.yml
Dockerfile*
tmp/
vendor/
bin/
*.test
*.prof

Security Best Practices

# 1. 使用特定版本标签,避免用 latest
FROM alpine:3.19    # 好
FROM alpine:latest  # 坏 -- 不可复现

# 2. 以非 root 用户运行
RUN addgroup -S app && adduser -S app -G app
USER app

# 3. 只读文件系统(运行时指定)
# docker run --read-only --tmpfs /tmp myapp

# 4. 不要在镜像中存储密钥
# 坏的做法
ENV DB_PASSWORD=secret123
# 好的做法:运行时通过环境变量或 Secret 管理工具注入

III. Docker Compose Orchestration

3.1 Why Docker Compose is Needed

In a microservices architecture, a Go project typically depends on multiple external services. Docker Compose allows defining and managing the orchestration of multiple containers using a single YAML file.

3.2 Complete Example: Go Service + MySQL + Redis + Consul

# docker-compose.yml
version: "3.9"

services:
  # ============ Go 应用服务 ============
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        - GOPROXY=https://goproxy.cn,direct
    container_name: go-app
    ports:
      - "8080:8080"    # 主机端口:容器端口
    environment:
      - PORT=8080
      - DB_HOST=mysql
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=rootpassword
      - DB_NAME=myapp
      - REDIS_ADDR=redis:6379
      - CONSUL_ADDR=consul:8500
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
      consul:
        condition: service_started
    networks:
      - app-network
    restart: unless-stopped
    # 资源限制
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M

  # ============ MySQL 数据库 ============
  mysql:
    image: mysql:8.0
    container_name: go-mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: myapp
      MYSQL_CHARSET: utf8mb4
    volumes:
      - mysql-data:/var/lib/mysql              # 数据持久化
      - ./deploy/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql  # 初始化脚本
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network
    restart: unless-stopped

  # ============ Redis 缓存 ============
  redis:
    image: redis:7-alpine
    container_name: go-redis
    ports:
      - "6379:6379"
    command: redis-server --requirepass redispassword --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "redispassword", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network
    restart: unless-stopped

  # ============ Consul 服务注册与发现 ============
  consul:
    image: hashicorp/consul:1.17
    container_name: go-consul
    ports:
      - "8500:8500"    # HTTP API 和 Web UI
      - "8600:8600/udp" # DNS
    command: agent -server -bootstrap-expect=1 -ui -client=0.0.0.0
    volumes:
      - consul-data:/consul/data
    networks:
      - app-network
    restart: unless-stopped

# ============ 数据卷定义 ============
volumes:
  mysql-data:
    driver: local
  redis-data:
    driver: local
  consul-data:
    driver: local

# ============ 网络定义 ============
networks:
  app-network:
    driver: bridge

3.3 Common Docker Compose Commands

# 启动所有服务(后台运行)
docker-compose up -d

# 启动并重新构建镜像
docker-compose up -d --build

# 查看服务状态
docker-compose ps

# 查看某个服务的日志
docker-compose logs -f app

# 停止所有服务
docker-compose down

# 停止并删除数据卷(慎用,会丢失数据)
docker-compose down -v

# 只启动某个服务及其依赖
docker-compose up -d app

# 进入某个服务的容器
docker-compose exec app sh

# 扩展服务实例数
docker-compose up -d --scale app=3

IV. Quick Reference for Common Docker Commands

4.1 Image Management

# 构建镜像
docker build -t myapp:v1.0 .
docker build -t myapp:v1.0 -f deploy/Dockerfile .   # 指定 Dockerfile 路径

# 查看本地镜像
docker images
docker image ls

# 删除镜像
docker rmi myapp:v1.0
docker image prune           # 清理悬空镜像(dangling images)
docker image prune -a        # 清理所有未使用的镜像

# 镜像标签
docker tag myapp:v1.0 registry.example.com/myapp:v1.0

# 导入导出(离线场景)
docker save -o myapp.tar myapp:v1.0
docker load -i myapp.tar

# 查看镜像构建历史 / 层信息
docker history myapp:v1.0
docker inspect myapp:v1.0

4.2 Container Management

# 运行容器
docker run -d --name myapp -p 8080:8080 myapp:v1.0
docker run -d --name myapp \
  -p 8080:8080 \
  -e DB_HOST=localhost \
  -v /host/data:/app/data \
  --restart unless-stopped \
  myapp:v1.0

# 查看运行中的容器
docker ps
docker ps -a                 # 包含已停止的容器

# 容器生命周期
docker start myapp
docker stop myapp
docker restart myapp
docker rm myapp              # 删除已停止的容器
docker rm -f myapp           # 强制删除(包括运行中的)

# 进入容器内部
docker exec -it myapp sh     # alpine 用 sh
docker exec -it myapp bash   # 带 bash 的镜像用 bash

# 查看日志
docker logs myapp
docker logs -f myapp         # 实时跟踪
docker logs --tail 100 myapp # 最后100行

# 查看容器资源使用
docker stats
docker stats myapp

# 复制文件
docker cp myapp:/app/logs/app.log ./app.log   # 容器到主机
docker cp ./config.yaml myapp:/app/config.yaml # 主机到容器

4.3 System Cleanup

# 一键清理:停止的容器、悬空镜像、未使用的网络
docker system prune

# 包含未使用的数据卷(慎用)
docker system prune --volumes

# 查看 Docker 磁盘使用情况
docker system df

V. Docker Network Modes

5.1 Bridge Mode (Default)

Each container has its own independent network namespace and communicates via the virtual bridge docker0:

# 创建自定义 bridge 网络
docker network create my-network

# 将容器连接到自定义网络
docker run -d --name app --network my-network myapp:v1.0
docker run -d --name db --network my-network mysql:8.0

# 同一网络内的容器可以通过容器名互相访问
# app 容器内可以用 "db:3306" 连接数据库

Advantages of custom bridge networks: - Containers discover each other by name (DNS) - Better network isolation - Ability to dynamically connect/disconnect containers

5.2 Host Mode

Containers directly use the host’s network, with no network isolation:

docker run -d --network host myapp:v1.0
# 容器中监听 8080 端口 = 宿主机 8080 端口
# 不需要 -p 端口映射
  • Pros: Best performance, no NAT overhead
  • Cons: Port conflict risk, lower security
  • Use cases: High-performance network requirements, network debugging

5.3 None Mode

Containers have no network connection, completely isolated:

docker run -d --network none myapp:v1.0
  • Use cases: Tasks that only require computation and no network access

5.4 Network Mode Comparison

ModeIsolationPerformanceUse Case
bridgeHighMediumDefault choice, most scenarios
hostNoneHighHigh-performance requirements
noneComplete-Security-sensitive computational tasks
overlayHighMediumCross-host Swarm/K8s

VI. Data Volumes and Persistence

6.1 Why Data Volumes are Needed

A container’s file system is temporary, and data is lost when the container is deleted. Data volumes provide a persistent storage mechanism.

6.2 Three Mount Types

# 1. 命名卷(Named Volume)-- 推荐用于数据持久化
docker volume create mydata
docker run -v mydata:/app/data myapp:v1.0

# 2. 绑定挂载(Bind Mount)-- 适合开发时挂载源代码
docker run -v /host/path:/container/path myapp:v1.0
docker run -v $(pwd)/config:/app/config:ro myapp:v1.0   # 只读挂载

# 3. tmpfs 挂载 -- 内存中的临时文件系统
docker run --tmpfs /app/tmp myapp:v1.0

6.3 Data Volume Management

# 查看所有卷
docker volume ls

# 查看卷详情
docker volume inspect mydata

# 删除卷
docker volume rm mydata

# 清理未使用的卷
docker volume prune

6.4 Typical Usage in Go Projects

# docker-compose.yml 中的卷使用
services:
  app:
    volumes:
      - ./config:/app/config:ro    # 配置文件(只读绑定)
      - app-logs:/app/logs         # 日志目录(命名卷)
      - /app/tmp                   # 匿名卷(临时文件)

  mysql:
    volumes:
      - mysql-data:/var/lib/mysql  # 数据库数据(命名卷,最重要)

volumes:
  app-logs:
  mysql-data:

VII. Docker’s Application in Microservices Development

7.1 Local Development Workflow

In a microservices architecture, a Go service may depend on multiple other services. Docker makes local development easy:

+-----------------------------------------------+
|           docker-compose Orchestration        |
|                                               |
|  +----------+  +----------+  +----------+    |
|  | User Service |  | Order Service |  | Product Service |    |
|  | Go :8081 |  | Go :8082 |  | Go :8083 |    |
|  +----+-----+  +----+-----+  +----+-----+    |
|       |              |              |          |
|  +----+--------------+--------------+----+    |
|  |          app-network (bridge)         |    |
|  +----+----------+----------+-----------+    |
|       |          |          |                 |
|  +----+---+ +---+----+ +--+------+           |
|  | MySQL  | | Redis  | | Consul  |           |
|  | :3306  | | :6379  | | :8500   |           |
|  +--------+ +--------+ +---------+           |
+-----------------------------------------------+

7.2 Hot Reload Development Mode

During development, tools like air can be combined to achieve code hot reloading:

# docker-compose.dev.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app                  # 挂载源代码
    command: air                 # 使用 air 热重载
    ports:
      - "8080:8080"
# Dockerfile.dev
FROM golang:1.22-alpine

RUN go install github.com/air-verse/air@latest

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

# 不需要 COPY . . 因为源代码通过 volume 挂载
CMD ["air"]

7.3 Integration Testing

Docker can easily set up an integration testing environment:

# 启动测试依赖
docker-compose -f docker-compose.test.yml up -d

# 运行集成测试
go test ./... -tags=integration

# 清理
docker-compose -f docker-compose.test.yml down -v

You can also dynamically create test containers in Go code using the testcontainers-go library:

package repository_test

import (
    "context"
    "testing"

    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/modules/mysql"
)

func TestUserRepository(t *testing.T) {
    ctx := context.Background()

    // 动态创建 MySQL 容器用于测试
    mysqlC, err := mysql.Run(ctx,
        "mysql:8.0",
        mysql.WithDatabase("testdb"),
        mysql.WithUsername("test"),
        mysql.WithPassword("test"),
    )
    if err != nil {
        t.Fatal(err)
    }
    defer mysqlC.Terminate(ctx)

    // 获取连接地址
    host, _ := mysqlC.Host(ctx)
    port, _ := mysqlC.MappedPort(ctx, "3306")

    // 使用真实数据库进行测试...
    t.Logf("MySQL running at %s:%s", host, port.Port())
}

VIII. Pushing Images to Registry

8.1 Pushing to Docker Hub

# 1. 登录
docker login

# 2. 给镜像打标签(格式:用户名/仓库名:标签)
docker tag myapp:v1.0 username/myapp:v1.0
docker tag myapp:v1.0 username/myapp:latest

# 3. 推送
docker push username/myapp:v1.0
docker push username/myapp:latest

8.2 Pushing to a Private Registry

# 以阿里云 ACR 为例
# 1. 登录私有仓库
docker login registry.cn-hangzhou.aliyuncs.com

# 2. 打标签
docker tag myapp:v1.0 registry.cn-hangzhou.aliyuncs.com/myns/myapp:v1.0

# 3. 推送
docker push registry.cn-hangzhou.aliyuncs.com/myns/myapp:v1.0

8.3 Setting up a Local Private Registry (Harbor)

Harbor is an enterprise-grade Docker image registry that supports features like access control and image scanning:

# 使用 Docker Compose 快速部署 Harbor
# 下载离线安装包后:
./install.sh --with-chartmuseum --with-trivy

# 推送到 Harbor
docker tag myapp:v1.0 harbor.example.com/myproject/myapp:v1.0
docker push harbor.example.com/myproject/myapp:v1.0

8.4 Automated Build and Push in CI/CD

Automatically build and push images in GitHub Actions:

# .github/workflows/docker.yml
name: Build and Push Docker Image

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            username/myapp:${{ github.ref_name }}
            username/myapp:latest

IX. Complete Practical Workflow for Dockerizing Go Projects

9.1 Project Structure

myapp/
├── cmd/
│   └── server/
│       └── main.go          # Entrypoint
├── internal/
│   ├── handler/             # HTTP Handlers
│   ├── service/             # Business Logic
│   ├── repository/          # Data Access
│   └── config/              # Configuration Management
├── deploy/
│   ├── mysql/
│   │   └── init.sql         # Database Initialization
│   └── nginx/
│       └── nginx.conf       # Reverse Proxy Configuration
├── go.mod
├── go.sum
├── Dockerfile
├── docker-compose.yml
├── docker-compose.dev.yml
├── .dockerignore
└── Makefile

9.2 Makefile Automation

# Makefile
APP_NAME=myapp
VERSION=$(shell git describe --tags --always --dirty)
REGISTRY=registry.example.com/myns

.PHONY: build run test docker-build docker-push deploy clean

# 本地编译
build:
    CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=$(VERSION)" \
        -o bin/$(APP_NAME) ./cmd/server

# 本地运行
run:
    go run ./cmd/server

# 运行测试
test:
    go test ./... -v -cover

# 构建 Docker 镜像
docker-build:
    docker build -t $(APP_NAME):$(VERSION) .
    docker tag $(APP_NAME):$(VERSION) $(APP_NAME):latest

# 推送镜像
docker-push: docker-build
    docker tag $(APP_NAME):$(VERSION) $(REGISTRY)/$(APP_NAME):$(VERSION)
    docker push $(REGISTRY)/$(APP_NAME):$(VERSION)

# 启动开发环境
dev:
    docker-compose -f docker-compose.dev.yml up -d --build

# 启动生产环境
deploy:
    docker-compose up -d --build

# 查看日志
logs:
    docker-compose logs -f app

# 清理
clean:
    docker-compose down -v
    docker image prune -f
    rm -rf bin/

9.3 Complete Production-Grade Dockerfile

# ============ 第一阶段:编译 ============
FROM golang:1.22-alpine AS builder

# 安装必要的构建工具
RUN apk add --no-cache git ca-certificates tzdata

# 设置 Go 环境
ENV GOPROXY=https://goproxy.cn,direct
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64

WORKDIR /build

# 依赖缓存层
COPY go.mod go.sum ./
RUN go mod download && go mod verify

# 编译
COPY . .
ARG VERSION=dev
RUN go build \
    -ldflags="-s -w -X main.Version=${VERSION}" \
    -o /build/app \
    ./cmd/server

# ============ 第二阶段:运行 ============
FROM alpine:3.19

LABEL maintainer="your-email@example.com"
LABEL version="${VERSION}"
LABEL description="My Go Application"

# 安装运行时依赖
RUN apk --no-cache add ca-certificates tzdata && \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone

# 创建非 root 用户和必要目录
RUN addgroup -S app && adduser -S app -G app && \
    mkdir -p /app/logs /app/config && \
    chown -R app:app /app

WORKDIR /app

# 复制二进制文件
COPY --from=builder /build/app .

# 复制配置文件模板(可选)
# COPY --from=builder /build/config ./config

# 切换到非 root 用户
USER app

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1

# 启动
ENTRYPOINT ["./app"]

9.4 Complete Workflow Summary

# 1. 编写代码和 Dockerfile
# 2. 本地开发和测试
make dev              # 启动开发环境
make test             # 运行测试

# 3. 构建镜像
make docker-build     # 构建生产镜像

# 4. 本地验证
make deploy           # 启动完整环境
make logs             # 查看日志
curl http://localhost:8080/health  # 测试接口

# 5. 推送镜像
make docker-push      # 推送到仓库

# 6. 部署到服务器
ssh your-server
docker pull registry.example.com/myns/myapp:v1.0
docker-compose up -d

# 7. 清理
make clean

X. Common Issues and Troubleshooting

10.1 Common Issues

IssueCauseSolution
Image size too largeMulti-stage build not usedUse multi-stage build
Slow buildCache missCOPY go.mod first, then COPY .
Incorrect timezone in containerMissing timezone dataInstall tzdata, set TZ
HTTPS request failedMissing CA certificatesapk add ca-certificates
Cannot connect to other containersNetwork connectivity issueEnsure they are in the same docker network
Data lossVolume not usedUse named volumes for important data

10.2 Debugging Tips

# 查看容器内部文件系统
docker exec -it myapp sh

# 查看容器网络配置
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' myapp

# 查看容器环境变量
docker exec myapp env

# 临时运行一个调试容器(和目标容器共享网络)
docker run -it --rm --network container:myapp alpine sh

# 查看 Docker 构建过程(详细输出)
docker build --progress=plain -t myapp . 2>&1 | tee build.log

Next Article: 016 - Introduction to Kubernetes, learn how to deploy Docker containers to a K8s cluster for production-grade orchestration and management.