Docker 初级指南

Docker 是一个开源的容器化平台,允许开发者将应用及其依赖打包到一个轻量级、可移植的容器中,在任何环境中一致地运行。 Image(镜像) 镜像是一个只读的模板,用于创建容器。它包含了运行应用所需的所有文件:操作系统文件、运行时、库、配置等。镜像采用分层结构,每一层都是只读的。 Container(容器) 容器是镜像的运行实例。在只读的镜像层之上,容器有一个可读写层。容器之间相互隔离,但共享宿主机的操作系统内核。容器停止后其可写层数据默认消失(需要卷来持久化)。 Registry(仓库) Registry 是存储和分发镜像的服务。常见的 Registr

分享

官方文档:https://docs.docker.com/
适用版本:Docker Engine 24+(2026-05-07 核实)

Docker 是一个开源的容器化平台,允许开发者将应用及其依赖打包到一个轻量级、可移植的容器中,在任何环境中一致地运行。


1. Docker 是什么

容器 vs 虚拟机

对比项 容器(Container) 虚拟机(VM)
启动速度 秒级(毫秒级) 分钟级
资源占用 极低,共享宿主机内核 较高,每个 VM 独立内核
隔离级别 进程级隔离(namespace/cgroup) 硬件级隔离(Hypervisor)
镜像大小 通常几十 MB 到数百 MB 通常几 GB 到几十 GB
可移植性 极高,跨平台一致运行 较高,但文件体积大
密度 单机可运行数百个容器 单机通常运行数十个 VM
安全性 相对较低(共享内核) 较高(完全隔离)

核心概念

Image(镜像)

镜像是一个只读的模板,用于创建容器。它包含了运行应用所需的所有文件:操作系统文件、运行时、库、配置等。镜像采用分层结构,每一层都是只读的。

Container(容器)

容器是镜像的运行实例。在只读的镜像层之上,容器有一个可读写层。容器之间相互隔离,但共享宿主机的操作系统内核。容器停止后其可写层数据默认消失(需要卷来持久化)。

Registry(仓库)

Registry 是存储和分发镜像的服务。常见的 Registry:

  • Docker Hub(hub.docker.com):官方公共仓库
  • 阿里云容器镜像服务:国内加速
  • GitHub Container Registry(ghcr.io
  • 私有 Registry:自建的 Harbor 等

Dockerfile

Dockerfile 是一个文本文件,包含一系列指令,描述如何构建一个镜像。每条指令对应镜像中的一层。

Docker Compose

Docker Compose 是用于定义和运行多容器 Docker 应用的工具。通过一个 docker-compose.yml 文件配置所有服务,然后用一条命令启动整个应用栈。

为什么使用 Docker

  • 环境一致性:开发、测试、生产环境完全一致,消除"在我机器上能跑"的问题
  • 快速部署:镜像一旦构建,几秒内即可启动容器,大幅缩短部署时间
  • 资源隔离:每个容器拥有独立的文件系统、网络、进程空间,互不干扰
  • 版本管理:镜像版本化,可以随时回滚到任意历史版本
  • 微服务友好:天然适合微服务架构,每个服务独立打包、独立扩展

2. 安装

Linux(Ubuntu / Debian)

# 卸载旧版本
sudo apt-get remove docker docker-engine docker.io containerd runc

# 更新包索引并安装依赖
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg

# 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# 添加 Docker 仓库
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 将当前用户加入 docker 组(避免每次 sudo)
sudo usermod -aG docker $USER
newgrp docker

# 启动并设置开机启动
sudo systemctl enable docker
sudo systemctl start docker

macOS(Docker Desktop)

  1. 访问 https://www.docker.com/products/docker-desktop
  2. 下载 macOS 版本(区分 Intel 和 Apple Silicon)
  3. 安装 .dmg 文件,拖拽到 Applications
  4. 启动 Docker Desktop,等待鲸鱼图标稳定

也可使用 Homebrew:

brew install --cask docker

Windows(Docker Desktop + WSL2)

前置条件:

# 以管理员身份在 PowerShell 中执行
# 启用 WSL
wsl --install

# 安装完成后重启,然后安装 Ubuntu 发行版
wsl --install -d Ubuntu

然后:

  1. 访问 https://www.docker.com/products/docker-desktop
  2. 下载 Windows 版 Docker Desktop
  3. 安装时勾选"Use WSL 2 instead of Hyper-V"
  4. 安装完成后重启
  5. 在 Docker Desktop 设置中,确认 WSL Integration 已开启

验证安装

# 查看版本
docker --version
# 输出示例:Docker version 24.0.5, build ced0996

# 查看详细信息(守护进程是否运行)
docker info

# 运行 hello-world 测试
docker run hello-world

hello-world 输出 "Hello from Docker!" 表示安装成功,它完整演示了 Docker 从拉取镜像到运行容器的全流程。


3. 核心概念与架构

Docker 架构

Docker 采用客户端-服务端架构:

Docker Client (docker CLI)
       |
       | REST API / Unix socket
       v
Docker Daemon (dockerd)  <----->  Registry (Docker Hub 等)
       |
       +-- Images(本地镜像存储)
       +-- Containers(运行中的容器)
       +-- Networks(网络)
       +-- Volumes(卷)
  • Docker Client:用户与 Docker 交互的命令行工具,发送命令给 Daemon
  • Docker Daemon(dockerd):后台运行的服务,负责构建、运行、管理容器
  • Registry:镜像仓库,Daemon 从这里拉取(pull)和推送(push)镜像

镜像分层机制(Layer)

Docker 镜像由多个只读层叠加而成。以下 Dockerfile 会产生多层:

FROM ubuntu:22.04        # 第1层:基础镜像
RUN apt-get update       # 第2层:更新包索引
RUN apt-get install -y nginx  # 第3层:安装 nginx
COPY index.html /var/www/html/ # 第4层:复制文件

层的核心特性:

  • 每条 RUNCOPYADD 指令产生一个新层
  • 层是只读且可复用的:多个镜像共享相同的层,节省磁盘空间
  • 拉取镜像时,已有的层不会重复下载
  • 构建时,未变化的层从缓存读取,加速构建

Union File System(联合文件系统)

Docker 使用联合文件系统(如 overlay2)将多个只读层和一个可写层合并成一个统一的文件系统视图。容器对文件的修改写入最顶层的可写层(容器层),不影响下方的只读镜像层。容器删除后,可写层随之消失。


4. 常用镜像操作命令

docker pull

从 Registry 拉取镜像。

docker pull [OPTIONS] NAME[:TAG|@DIGEST]
参数 类型 默认值 说明
-a, --all-tags flag false 拉取仓库中所有 tag 的镜像
--platform string 当前平台 指定目标平台,如 linux/amd64linux/arm64
-q, --quiet flag false 静默模式,不显示进度条
--disable-content-trust flag true 跳过镜像签名验证
# 拉取最新版 nginx(等同于 nginx:latest)
docker pull nginx

# 拉取指定 tag
docker pull nginx:alpine
docker pull ubuntu:22.04

# 拉取指定 digest(精确版本,不可变)
docker pull nginx@sha256:abc123...

# 为 ARM 设备拉取 AMD64 镜像
docker pull --platform linux/amd64 nginx

docker images / docker image ls

列出本地镜像。

docker images [OPTIONS] [REPOSITORY[:TAG]]
参数 类型 默认值 说明
-a, --all flag false 显示所有镜像,含构建中间层
-q, --quiet flag false 只输出镜像 ID
--format string - 自定义输出格式(Go 模板)
-f, --filter string - 过滤条件,如 dangling=true
--no-trunc flag false 显示完整的镜像 ID
--digests flag false 显示 digest 信息
# 列出所有本地镜像
docker images

# 只输出 ID(用于批量操作)
docker images -q

# 过滤悬空镜像(没有 tag 的镜像,通常是构建遗留)
docker images -f dangling=true

# 自定义格式输出
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# 只显示 nginx 相关镜像
docker images nginx

docker rmi / docker image rm

删除本地镜像。

docker rmi [OPTIONS] IMAGE [IMAGE...]
参数 类型 默认值 说明
-f, --force flag false 强制删除,即使有容器依赖此镜像
--no-prune flag false 不删除未被 tag 的父镜像
# 删除指定镜像
docker rmi nginx:alpine

# 强制删除(有容器在用时)
docker rmi -f nginx:alpine

# 删除多个镜像
docker rmi nginx ubuntu:22.04

# 通过 ID 删除
docker rmi abc123def456

# 批量删除所有悬空镜像
docker image prune

# 清理所有未被任何容器使用的镜像(谨慎)
docker image prune -a

# 删除所有镜像(极度谨慎)
docker rmi $(docker images -q)

在 Docker Hub 中搜索镜像。

docker search [OPTIONS] TERM
参数 类型 默认值 说明
--limit int 25 最大返回结果数
-f, --filter string - 过滤条件
--format string - 自定义输出格式
--no-trunc flag false 显示完整描述

常用过滤条件:

过滤器 示例 说明
stars=N stars=100 最少 N 个 star
is-official=true is-official=true 只显示官方镜像
is-automated=true is-automated=true 只显示自动构建镜像
# 搜索 nginx
docker search nginx

# 只看官方镜像且 star 数超过 1000
docker search --filter is-official=true --filter stars=1000 nginx

# 限制结果数
docker search --limit 5 python

docker tag

为镜像打标签,常用于推送前重命名。

docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
# 为本地镜像打 tag,准备推送到私有仓库
docker tag myapp:1.0 registry.example.com/myteam/myapp:1.0

# 打 latest tag
docker tag myapp:1.0 myapp:latest

# 推送到 Docker Hub(需先 docker login)
docker tag myapp:1.0 username/myapp:1.0
docker push username/myapp:1.0

5. 容器操作命令

docker run

创建并启动一个新容器,是最核心的命令。

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
参数 类型 默认值 说明
-d, --detach flag false 后台运行容器,返回容器 ID
-i, --interactive flag false 保持 stdin 打开
-t, --tty flag false 分配伪终端(TTY)
--name string 随机生成 指定容器名称,便于引用
-p, --publish string - 端口映射,格式:HOST_PORT:CONTAINER_PORTHOST_PORT:CONTAINER_PORT/tcp
-P, --publish-all flag false 将所有 EXPOSE 的端口映射到宿主机随机端口
-v, --volume string - 挂载卷,格式:HOST_PATH:CONTAINER_PATH[:ro]
--mount string - 更清晰的挂载语法,推荐用于复杂挂载
-e, --env string - 设置环境变量,如 -e KEY=VALUE
--env-file string - 从文件批量读取环境变量
--network string bridge 网络模式:bridgehostnone、自定义网络名
--rm flag false 容器退出后自动删除容器(不删除卷)
--restart string no 重启策略
-w, --workdir string - 容器内的工作目录
-u, --user string - 运行命令的用户,格式:useruser:group
-m, --memory string - 内存限制,如 512m1g
--memory-swap string - 内存+Swap 总限制
--cpus float - CPU 核数限制,如 0.52
--cpu-shares int 1024 CPU 相对权重
--platform string 当前平台 指定镜像平台
--entrypoint string - 覆盖镜像的 ENTRYPOINT
-l, --label string - 添加元数据标签
--hostname string 容器ID 容器主机名
--add-host string - 添加 hosts 记录,格式 host:ip
--privileged flag false 给予容器扩展权限(谨慎使用)
--read-only flag false 将容器根文件系统设为只读

重启策略说明:

策略 说明
no 不自动重启(默认)
always 总是重启,包括 Docker 守护进程重启后
on-failure[:N] 仅在非零退出码时重启,可限制次数
unless-stopped 总是重启,除非手动 stop
# 后台运行 nginx,将宿主机 80 端口映射到容器 80 端口
docker run -d -p 80:80 --name my-nginx nginx

# 交互式运行 ubuntu,退出后自动删除容器
docker run -it --rm ubuntu:22.04 bash

# 挂载当前目录到容器 /app,设置工作目录,安装依赖
docker run -v $(pwd):/app -w /app node:18 npm install

# 传入环境变量运行 PostgreSQL
docker run -d \
  --name my-postgres \
  -e POSTGRES_USER=admin \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=mydb \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  --restart unless-stopped \
  postgres:15

# 限制资源运行
docker run -d --memory 512m --cpus 1.0 --name limited-app myapp

# 从环境变量文件启动
docker run --env-file .env myapp

# 只读根文件系统(安全加固)
docker run --read-only --tmpfs /tmp myapp

# 覆盖 ENTRYPOINT,进入 shell 调试
docker run -it --entrypoint bash nginx

docker start / stop / restart / kill

对已有容器进行生命周期管理。

# docker start:启动已停止的容器
docker start [OPTIONS] CONTAINER [CONTAINER...]
参数 类型 默认值 说明
-a, --attach flag false 附加到容器的 stdout/stderr
-i, --interactive flag false 附加容器的 stdin
# docker stop:优雅停止容器(发送 SIGTERM,等待后发送 SIGKILL)
docker stop [OPTIONS] CONTAINER [CONTAINER...]
参数 类型 默认值 说明
-t, --time int 10 等待容器停止的超时秒数
-s, --signal string SIGTERM 发送的停止信号
# 示例
docker start my-nginx
docker stop my-nginx
docker stop -t 30 my-nginx    # 等待最多 30 秒

docker restart my-nginx
docker restart -t 5 my-nginx  # 5 秒后强制重启

# docker kill:立即发送信号(默认 SIGKILL)
docker kill [OPTIONS] CONTAINER [CONTAINER...]
参数 类型 默认值 说明
-s, --signal string KILL 发送的信号名或编号
docker kill my-nginx
docker kill -s SIGINT my-nginx

docker ps / docker container ls

列出容器。

docker ps [OPTIONS]
参数 类型 默认值 说明
-a, --all flag false 显示所有容器,含已停止的
-q, --quiet flag false 只输出容器 ID
-f, --filter string - 过滤条件
--format string - 自定义输出格式
-n, --last int -1 显示最近创建的 N 个容器
-l, --latest flag false 显示最近创建的容器
-s, --size flag false 显示容器文件大小
--no-trunc flag false 不截断输出

常用过滤条件:

过滤器 示例 说明
status status=running 按状态过滤:created/running/paused/exited/dead
name name=my-nginx 按名称过滤(支持正则)
label label=env=prod 按标签过滤
ancestor ancestor=nginx 基于某镜像创建的容器
# 查看运行中的容器
docker ps

# 查看所有容器(含已停止)
docker ps -a

# 只看 ID(用于批量操作)
docker ps -q

# 查看已停止的容器
docker ps -f status=exited

# 自定义输出格式
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

# 停止所有运行中的容器
docker stop $(docker ps -q)

docker exec

在运行中的容器内执行命令。

docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
参数 类型 默认值 说明
-i, --interactive flag false 保持 stdin 打开
-t, --tty flag false 分配伪终端
-d, --detach flag false 在后台执行命令
-e, --env string - 设置环境变量
-u, --user string - 指定执行用户
-w, --workdir string - 指定工作目录
--privileged flag false 给予命令扩展权限
# 进入容器 bash(最常用)
docker exec -it my-nginx bash

# 进入容器 sh(当容器没有 bash 时)
docker exec -it my-nginx sh

# 在容器内执行单条命令
docker exec my-nginx nginx -t

# 以 root 身份进入容器
docker exec -it -u root my-app bash

# 在指定工作目录执行
docker exec -w /app my-app ls -la

# 后台执行命令
docker exec -d my-nginx touch /tmp/testfile

docker logs

获取容器的日志输出。

docker logs [OPTIONS] CONTAINER
参数 类型 默认值 说明
-f, --follow flag false 实时跟踪日志输出
--tail string all 只显示最后 N 行
--since string - 显示指定时间点之后的日志,如 2024-01-011h
--until string - 显示指定时间点之前的日志
-t, --timestamps flag false 显示时间戳
--details flag false 显示额外的日志详情
# 查看容器日志
docker logs my-nginx

# 实时跟踪(类似 tail -f)
docker logs -f my-nginx

# 只看最后 100 行
docker logs --tail 100 my-nginx

# 实时跟踪最后 50 行开始
docker logs -f --tail 50 my-nginx

# 显示时间戳
docker logs -t my-nginx

# 查看最近 1 小时的日志
docker logs --since 1h my-nginx

# 查看指定时间范围
docker logs --since "2024-01-01T10:00:00" --until "2024-01-01T11:00:00" my-nginx

docker cp

在容器与宿主机之间复制文件。

docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH
docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH
参数 类型 默认值 说明
-a, --archive flag false 保留文件所有者和权限(Archive 模式)
-L, --follow-link flag false 跟随 SRC_PATH 中的符号链接
-q, --quiet flag false 静默模式
# 从容器复制文件到宿主机
docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf

# 从宿主机复制文件到容器
docker cp ./index.html my-nginx:/usr/share/nginx/html/index.html

# 复制整个目录
docker cp my-app:/app/logs ./logs

# 复制到容器时保留权限
docker cp -a ./config.json my-app:/etc/app/config.json

docker rm

删除已停止的容器。

docker rm [OPTIONS] CONTAINER [CONTAINER...]
参数 类型 默认值 说明
-f, --force flag false 强制删除运行中的容器(发送 SIGKILL)
-v, --volumes flag false 同时删除容器关联的匿名卷
-l, --link flag false 删除容器间的网络连接(不删除容器)
# 删除已停止的容器
docker rm my-nginx

# 强制删除运行中的容器
docker rm -f my-nginx

# 删除容器及其匿名卷
docker rm -v my-nginx

# 清理所有已停止的容器
docker container prune

# 删除所有容器(包括运行中的,谨慎)
docker rm -f $(docker ps -aq)

docker inspect

获取容器或镜像的详细配置信息(JSON 格式)。

docker inspect [OPTIONS] NAME|ID [NAME|ID...]
参数 类型 默认值 说明
-f, --format string - 使用 Go 模板格式化输出
--type string - 指定类型:containerimagenetworkvolume
-s, --size flag false 显示容器文件系统大小(仅容器)
# 查看容器全部信息
docker inspect my-nginx

# 只获取容器 IP 地址
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-nginx

# 获取挂载点信息
docker inspect -f '{{json .Mounts}}' my-nginx | python3 -m json.tool

# 获取环境变量
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' my-nginx

# 获取容器重启策略
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' my-nginx

docker stats

实时显示容器资源使用情况。

docker stats [OPTIONS] [CONTAINER...]
参数 类型 默认值 说明
-a, --all flag false 显示所有容器(含停止的)
--no-stream flag false 只输出一次,不持续刷新
--no-trunc flag false 不截断容器 ID
--format string - 自定义输出格式
# 实时查看所有运行容器的资源使用
docker stats

# 查看指定容器
docker stats my-nginx my-postgres

# 只输出一次(用于脚本)
docker stats --no-stream

# 自定义格式
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

6. Dockerfile 基础

基本结构示例

以下是一个完整的 Node.js 应用 Dockerfile:

# 指定基础镜像和版本(使用具体版本而非 latest)
FROM node:18-alpine

# 设置维护者信息(元数据)
LABEL maintainer="[email protected]"
LABEL version="1.0"

# 设置工作目录(不存在会自动创建)
WORKDIR /app

# 先复制 package 文件,利用层缓存
# 只有 package.json 变化时才重新 npm install
COPY package*.json ./

# 安装生产依赖(alpine 镜像已有 npm)
RUN npm ci --only=production

# 再复制其余源代码
COPY . .

# 声明应用监听的端口(文档作用,不自动映射)
EXPOSE 3000

# 创建非 root 用户提升安全性
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# 设置健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

# 容器启动时执行的命令(exec 形式,推荐)
CMD ["node", "server.js"]

常用指令完整参考

指令 语法示例 说明
FROM FROM ubuntu:22.04 指定基础镜像,必须是第一条非注释指令
RUN RUN apt-get install -y curl 执行命令并提交结果为新层
COPY COPY src/ /app/src/ 从构建上下文复制文件,推荐使用
ADD ADD archive.tar.gz /app/ 类似 COPY,额外支持 URL 和自动解压 tar
WORKDIR WORKDIR /app 设置后续指令的工作目录
ENV ENV NODE_ENV=production 设置环境变量,运行时可见
ARG ARG BUILD_VERSION=1.0 构建参数,仅构建阶段可见
EXPOSE EXPOSE 8080 声明容器监听端口(仅文档作用)
CMD CMD ["node", "app.js"] 默认启动命令,可被 docker run 末尾参数覆盖
ENTRYPOINT ENTRYPOINT ["nginx", "-g", "daemon off;"] 入口点,不易被覆盖
LABEL LABEL version="1.0" 添加键值元数据
USER USER 1001 切换运行用户
VOLUME VOLUME ["/data"] 声明挂载点
HEALTHCHECK HEALTHCHECK CMD curl -f http://localhost/ 定义健康检查命令
ONBUILD ONBUILD COPY . /app 仅当此镜像作为基础镜像时触发
STOPSIGNAL STOPSIGNAL SIGTERM 设置停止容器时发送的信号

FROM

# 基本用法
FROM ubuntu:22.04

# 使用摘要锁定版本
FROM ubuntu@sha256:abc123...

# 多阶段构建中命名阶段
FROM node:18 AS builder
FROM nginx:alpine AS final

RUN

RUN 有两种形式:

# Shell 形式(通过 /bin/sh -c 执行,支持 shell 特性)
RUN apt-get update && apt-get install -y \
    curl \
    wget \
    && rm -rf /var/lib/apt/lists/*

# Exec 形式(直接执行,不通过 shell,推荐用于明确路径)
RUN ["apt-get", "install", "-y", "curl"]

建议在一条 RUN 中链接多条命令,减少层数:

# 不推荐:产生多个层
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# 推荐:一层完成,且清理缓存
RUN apt-get update \
    && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*

COPY vs ADD

特性 COPY ADD
复制本地文件 支持 支持
支持 URL 下载 不支持 支持
自动解压 tar 不支持 支持(本地 tar)
可预测性 低(行为复杂)
推荐程度 推荐 不推荐(除非需要解压)
# COPY 用法
COPY package.json /app/
COPY src/ /app/src/
COPY --chown=appuser:appgroup . /app/   # 指定所有者

# ADD 用法(仅在需要解压时使用)
ADD https://example.com/file.tar.gz /tmp/   # 下载并解压
ADD archive.tar.gz /app/                    # 自动解压

ENV 与 ARG

# ENV:运行时环境变量,容器运行后也可见
ENV NODE_ENV=production
ENV PORT=3000 HOST=0.0.0.0   # 一行设置多个

# ARG:构建参数,仅在 docker build 时可用
ARG BUILD_DATE
ARG VERSION=1.0.0

# ARG 和 ENV 结合使用
ARG APP_VERSION=latest
ENV APP_VERSION=${APP_VERSION}   # 将构建参数转为环境变量
# 构建时传入 ARG 值
docker build --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
             --build-arg VERSION=2.0.0 \
             -t myapp:2.0.0 .

HEALTHCHECK

参数 类型 默认值 说明
--interval duration 30s 两次检查的间隔时间
--timeout duration 30s 单次检查的超时时间
--start-period duration 0s 容器启动后等待多久才开始检查
--retries int 3 连续失败多少次才判定为 unhealthy
# HTTP 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 禁用基础镜像的健康检查
HEALTHCHECK NONE

CMD 与 ENTRYPOINT 的关系

组合方式 ENTRYPOINT CMD 实际执行命令
只有 CMD ["node", "app.js"] node app.js
只有 ENTRYPOINT ["node"] node
两者都有 ["node"] ["app.js"] node app.js
docker run 附加参数 ["node"] ["app.js"] node <你的参数>(CMD 被覆盖)
docker run --entrypoint <被覆盖> ["app.js"] <新entrypoint> app.js

最佳实践:

  • ENTRYPOINT 定义固定的可执行程序
  • CMD 提供默认参数,允许用户在 docker run 时覆盖
  • 两者都应使用 exec 形式(JSON 数组),避免 shell 形式(PID 1 问题)
# 推荐写法:exec 形式
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

# 不推荐:shell 形式(nginx 不是 PID 1,无法接收信号)
ENTRYPOINT nginx -g "daemon off;"

.dockerignore 文件

类似 .gitignore,防止不必要的文件进入构建上下文,加快构建速度:

# .dockerignore

# 版本控制
.git
.gitignore

# Node.js
node_modules
npm-debug.log

# 构建产物
dist
build
*.log

# 环境配置
.env
.env.*
*.env

# IDE 配置
.idea
.vscode
*.swp

# 文档
README.md
docs/

# 测试
__tests__
*.test.js
coverage/

# Docker 相关(避免递归)
Dockerfile*
docker-compose*
.dockerignore

构建命令 docker build

docker build [OPTIONS] PATH | URL | -
参数 类型 默认值 说明
-t, --tag string - 镜像名称和标签,格式 name:tag
-f, --file string ./Dockerfile 指定 Dockerfile 文件路径
--no-cache flag false 不使用缓存,全量重建所有层
--build-arg string - 传入 ARG 变量值
--platform string 当前平台 目标平台,如 linux/amd64
--target string - 多阶段构建中指定目标阶段名称
--progress string auto 输出格式:autoplaintty
--pull flag false 总是拉取最新基础镜像
--label string - 为镜像设置元数据
-q, --quiet flag false 静默模式,只输出镜像 ID
# 基本构建
docker build -t myapp:1.0 .

# 指定 Dockerfile
docker build -f docker/Dockerfile.prod -t myapp:prod .

# 不使用缓存(排查问题时使用)
docker build --no-cache -t myapp:1.0 .

# 传入构建参数
docker build --build-arg VERSION=2.0 --build-arg ENV=prod -t myapp:2.0 .

# 多阶段构建,只构建到 builder 阶段
docker build --target builder -t myapp:builder .

# 跨平台构建(需要 buildx)
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0 --push .

7. Docker Compose 基础

安装

Docker Compose 有两个版本:

版本 命令 说明
V2 插件版(推荐) docker compose 集成在 Docker CLI 中,Docker Desktop 默认包含
V1 独立工具(旧) docker-compose 独立安装的 Python 脚本,已停止维护
# 验证 Compose 插件版本
docker compose version

# Linux 手动安装 Compose 插件(如果没有)
sudo apt-get install docker-compose-plugin

# 查看 V1 版本(如果已安装)
docker-compose --version

基本 docker-compose.yml 结构

以下是一个包含 Nginx、Node.js 应用、PostgreSQL 的完整示例:

# docker-compose.yml

version: "3.9"   # Compose 文件规范版本

services:
  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    container_name: proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - app
    networks:
      - frontend
    restart: unless-stopped

  # Node.js 应用
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        NODE_ENV: production
    container_name: node-app
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://admin:secret@db:5432/mydb
      - PORT=3000
    env_file:
      - .env
    volumes:
      - app-logs:/app/logs
    depends_on:
      db:
        condition: service_healthy
    networks:
      - frontend
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # PostgreSQL 数据库
  db:
    image: postgres:15-alpine
    container_name: postgres
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    ports:
      - "5432:5432"   # 开发环境暴露,生产环境可移除
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U admin -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5

# 具名卷(由 Docker 管理,数据持久化)
volumes:
  pgdata:
    driver: local
  app-logs:
    driver: local

# 自定义网络
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true   # 内部网络,不能访问外网

常用 Compose 命令

命令 说明
docker compose up 创建并启动所有服务(前台运行)
docker compose up -d 创建并启动所有服务(后台运行)
docker compose up --build 构建镜像后启动(有代码变更时)
docker compose up service_name 只启动指定服务
docker compose down 停止并删除容器、网络
docker compose down -v 停止并删除容器、网络、卷
docker compose down --rmi all 停止并删除容器、镜像
docker compose ps 查看服务状态
docker compose logs 查看所有服务日志
docker compose logs -f app 跟踪指定服务日志
docker compose exec app bash 进入指定服务容器
docker compose build 构建所有服务的镜像
docker compose build app 只构建指定服务
docker compose pull 拉取所有服务镜像
docker compose restart 重启所有服务
docker compose restart app 重启指定服务
docker compose stop 停止所有服务(不删除容器)
docker compose start 启动已停止的服务
docker compose rm 删除已停止的服务容器
docker compose config 验证并输出最终配置
docker compose run app npm test 一次性运行命令(新建容器)
docker compose scale app=3 扩展服务实例数量
# 启动所有服务(后台)
docker compose up -d

# 有代码变更时重新构建并启动
docker compose up -d --build

# 查看实时日志
docker compose logs -f

# 进入 app 服务容器
docker compose exec app bash

# 在 app 容器中运行迁移(临时容器,结束自动删除)
docker compose run --rm app npm run migrate

# 停止所有服务但保留容器和卷
docker compose stop

# 彻底清理(删除容器、网络、卷)
docker compose down -v

8. 数据管理基础

三种挂载方式对比

类型 命令行语法 数据位置 适用场景
Volume(具名卷) -v myvolume:/app/data Docker 管理目录 生产数据持久化(推荐)
Bind Mount -v /host/path:/container/path 宿主机任意路径 开发时同步代码、配置
tmpfs --tmpfs /app/tmp 宿主机内存 临时数据,不持久化,高性能

具名卷 vs 匿名卷 vs Bind Mount 详细对比:

特性 具名卷 匿名卷 Bind Mount
名称 自定义,如 pgdata Docker 自动生成 UUID 宿主机路径
创建方式 docker volume create 或 Compose docker run -v /path docker run -v /host:/container
生命周期 独立于容器,容器删除后保留 容器删除时默认保留(-v 删除) 由宿主机管理
备份难度 简单 困难(难以找到路径) 直接操作宿主机路径
推荐程度 生产环境推荐 不推荐 开发环境推荐

Volume 管理命令

# 创建具名卷
docker volume create mydata

# 列出所有卷
docker volume ls

# 查看卷详细信息
docker volume inspect mydata

# 删除指定卷
docker volume rm mydata

# 清理未使用的卷(谨慎)
docker volume prune

# 在 docker run 中使用具名卷
docker run -v mydata:/app/data myapp

# 只读挂载
docker run -v mydata:/app/data:ro myapp

--mount 语法(更清晰的挂载方式)

# Volume 挂载
docker run --mount type=volume,source=mydata,target=/app/data myapp

# Bind Mount
docker run --mount type=bind,source=/host/path,target=/container/path myapp

# 只读 Bind Mount
docker run --mount type=bind,source=/host/config,target=/etc/app,readonly myapp

# tmpfs
docker run --mount type=tmpfs,target=/app/tmp,tmpfs-size=100m myapp

Compose 中声明卷

services:
  app:
    volumes:
      - pgdata:/var/lib/postgresql/data   # 具名卷
      - ./src:/app/src                    # Bind Mount
      - /tmp/cache:/app/cache             # 绑定到宿主机临时目录

volumes:
  pgdata:              # 由 Docker 管理
  external-vol:
    external: true     # 使用已存在的外部卷(不由 Compose 创建)

9. 网络基础

默认网络模式对比

模式 说明 使用场景
bridge 默认模式,容器有独立网络栈,通过虚拟网桥与宿主机通信 单机多容器通信
host 容器共享宿主机网络命名空间,无端口映射,性能最高 高性能网络,Linux 专用(macOS/Windows 不支持)
none 容器无网络接口,完全隔离 安全敏感的无网络场景
container:NAME 共享另一个容器的网络命名空间 sidecar 模式
自定义 bridge 用户创建的桥接网络,支持容器名 DNS 解析 多容器应用(推荐)

注意:默认 bridge 网络不支持通过容器名互访(需 IP),自定义 bridge 网络支持容器名 DNS 解析。

常用网络命令

# 创建自定义网络
docker network create mynet

# 创建指定子网的网络
docker network create --subnet 172.20.0.0/16 mynet

# 列出所有网络
docker network ls

# 查看网络详细信息
docker network inspect mynet

# 删除网络
docker network rm mynet

# 清理未使用的网络
docker network prune

# 将运行中的容器加入网络
docker network connect mynet my-nginx

# 将容器从网络移除
docker network disconnect mynet my-nginx

# 运行时指定网络
docker run --network mynet --name app1 myapp

# 同一自定义网络中,容器可通过容器名互访
docker run --network mynet --name db postgres
docker run --network mynet --name app myapp
# app 容器内可通过 db:5432 访问数据库

10. 常见问题与踩坑

问题 1:docker run 没加 -d 导致终端卡住

现象:运行 docker run nginx 后终端被占用,Ctrl+C 会停止容器。

解决:后台运行加 -d 参数。

# 错误方式
docker run nginx

# 正确方式
docker run -d nginx

# 如果需要查看日志
docker logs -f <container_id>

问题 2:端口冲突

现象docker run -p 80:80 nginx 报错 Bind for 0.0.0.0:80 failed: port is already allocated

解决

# 查找占用端口的进程(Linux/macOS)
lsof -i :80

# Windows
netstat -ano | findstr :80

# 方案一:停止占用端口的进程
# 方案二:映射到其他端口
docker run -d -p 8080:80 nginx

问题 3:容器内无法连接宿主机服务

现象:容器内访问 localhost:3000 连接失败(如容器内应用访问宿主机上的数据库)。

原因:容器有独立网络命名空间,localhost 是容器自身,不是宿主机。

解决

# macOS 和 Windows Docker Desktop:使用特殊域名
host.docker.internal

# Linux:使用 docker0 网关 IP(通常是 172.17.0.1)
# 或者运行时加 --add-host
docker run --add-host=host.docker.internal:host-gateway myapp

# 在代码中
# 错误:localhost:5432
# 正确:host.docker.internal:5432

问题 4:容器删除后数据丢失

现象:删除容器后,容器内写入的数据全部消失。

原因:容器层是临时可写层,容器删除时一并删除。

解决:必须使用卷或 Bind Mount 持久化数据。

# 数据库必须挂载卷
docker run -d \
  -v pgdata:/var/lib/postgresql/data \
  postgres:15

# 验证数据持久化
docker rm postgres-container
docker run -d \
  -v pgdata:/var/lib/postgresql/data \
  postgres:15
# 数据仍然存在

问题 5:镜像层缓存导致全量重建

现象:修改一行代码后,docker build 需要重新安装所有依赖,耗时很长。

原因:COPY 的顺序不当,源代码与依赖文件一起复制,任何改动都使缓存失效。

解决:利用层缓存,先复制依赖描述文件,安装依赖,再复制源代码。

# 错误顺序:每次代码变更都重新 npm install
COPY . .
RUN npm install

# 正确顺序:只有 package.json 变更才重新 npm install
COPY package*.json ./
RUN npm ci
COPY . .

问题 6:时区问题

现象:容器内日志时间比实际时间少 8 小时,应用输出 UTC 时间。

原因:Docker 容器默认使用 UTC 时区。

解决

# 方式一:运行时设置环境变量
docker run -e TZ=Asia/Shanghai myapp

# 方式二:Dockerfile 中设置
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 方式三:挂载宿主机时区文件(Linux)
docker run -v /etc/localtime:/etc/localtime:ro myapp

问题 7:Linux bind mount 权限不匹配

现象:容器无法读写 bind mount 的文件,报 Permission denied。

原因:容器内进程的 UID/GID 与宿主机文件所有者不匹配。

解决

# 方式一:在 Dockerfile 中创建与宿主机 UID 匹配的用户
ARG UID=1000
RUN adduser --uid ${UID} --disabled-password appuser
USER appuser
# 方式二:运行时指定用户(匹配宿主机当前用户 UID)
docker run -u $(id -u):$(id -g) -v $(pwd):/app myapp

# 方式三:修改宿主机目录权限(不推荐,影响安全)
chmod -R 777 ./data

常用清理命令汇总

# 清理停止的容器
docker container prune

# 清理未使用的镜像
docker image prune

# 清理未使用的镜像(包括有 tag 的)
docker image prune -a

# 清理未使用的卷(谨慎!)
docker volume prune

# 清理未使用的网络
docker network prune

# 一键清理所有未使用的资源(容器、镜像、网络、构建缓存)
docker system prune

# 包括卷的全面清理(极度谨慎)
docker system prune --volumes

# 查看 Docker 磁盘占用
docker system df


最佳实践

Dockerfile 指令按变化频率从低到高排列:变化少的指令(FROMRUN apt install)放在前面,变化多的(COPY . .RUN pip install -r requirements.txt)放在后面,充分利用构建缓存,避免每次修改代码都重新安装依赖。

FROM python:3.12-slim
# 先安装依赖(很少变化)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再复制代码(经常变化)
COPY . .
CMD ["python", "app.py"]

使用 .dockerignore 减小构建上下文大小COPY . . 会把整个构建目录发送给 Docker daemon,.gitnode_modules__pycache__.env 等无需进入镜像的内容应写入 .dockerignore,避免镜像体积膨胀和构建变慢。

容器内不要以 root 用户运行:默认 root 用户在容器逃逸时具有主机 root 权限,存在安全风险。在 Dockerfile 末尾创建非 root 用户并切换。

RUN useradd -m appuser
USER appuser

一个容器只跑一个进程:不要在单个容器中同时运行 Web 服务 + 数据库 + Nginx,违反单一职责使容器难以扩展、监控和重启。多进程应拆分为多个容器,用 docker compose 协调。

用多阶段构建减小生产镜像体积:编译/构建阶段只在第一个 FROM 中完成,最终镜像只包含运行所需的产物。Go / Node / Java 项目镜像体积可减小 10 倍以上。

FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

常见陷阱

陷阱:每次修改代码都触发依赖重新安装

现象: docker build 时,即使只改了一行业务代码,npm installpip install 也重新执行,构建速度极慢。

原因: COPY . . 放在安装依赖之前,任何文件变化都会使缓存失效,导致后续所有层重新执行。

解决: 先只 COPY 依赖文件(package.jsonrequirements.txt),安装完成后再 COPY 其余代码。

陷阱:容器内修改文件后重启丢失

现象: 在运行中的容器内创建了文件或修改了数据库,容器重启后数据消失。

原因: 容器的可写层(writable layer)在容器删除或重新创建时被丢弃,容器内的文件系统不持久化。

解决: 使用 docker volume 将需要持久化的目录挂载为卷,或用 -v /host/path:/container/path 绑定挂载。

docker run -v db_data:/var/lib/postgresql/data postgres

陷阱:镜像中包含敏感信息(密码、密钥)

现象: 将密钥写入 Dockerfile 的 ENVRUN 层,构建后任何人 docker history 都能看到明文。

原因: Dockerfile 中每一层的内容都保存在镜像的 layer 元数据中,即使在后续层删除了该 ENV,仍可通过历史层读取。

解决: 绝不在 Dockerfile 中硬编码密钥;运行时通过环境变量(docker run -e)或 Docker Secrets 注入,配合 .dockerignore 排除 .env 文件。


参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog