Skip to content

Docker 常用命令

大白话解释: Docker 就像"集装箱"。把你的应用和它需要的所有东西(代码、运行环境、依赖)打包成一个"集装箱"(镜像),然后在任何安装了 Docker 的机器上都能运行。

为什么要用 Docker?

  • 环境一致性:开发环境和生产环境完全一样,不会出现"在我电脑上能跑"的问题
  • 快速部署:打包成镜像后,一条命令就能启动,不用手动配置环境
  • 隔离性:每个容器独立运行,互不影响
  • 易于扩展:需要更多实例?复制几个容器就行

Docker 的核心概念:

  • 镜像(Image):集装箱的模板,只读的
  • 容器(Container):用镜像创建的实例,可以运行
  • 卷(Volume):容器的"外挂硬盘",数据持久化

Docker 是容器化平台,用于打包、分发和运行应用。

安装

bash
# 验证安装
docker --version
docker compose version

Docker Desktop 常用功能

Docker Desktop 是 Windows/macOS 上的一体化 Docker 环境,内置以下功能:

功能说明
Docker Engine核心容器运行时
Docker Compose多容器编排
Docker Scout镜像安全漏洞扫描
Kubernetes内置 K8s 集群(需在设置中启用)
Extensions插件扩展市场
Dev Environments基于 Git 仓库快速创建开发环境
Volume Management可视化管理数据卷
Resource Limits在 Settings → Resources 中配置 CPU/内存/磁盘限制
bash
# Docker Desktop 内置的实用命令
docker scout cves my-app:latest    # 安全扫描
docker context ls                   # 查看上下文(含 Desktop 的远程引擎)

💡 Docker Desktop 免费供个人开发和小型企业使用,大型企业需购买订阅。Linux 用户直接安装 Docker Engine 即可。

镜像操作

bash
# 搜索镜像
docker search nginx

# 拉取镜像
docker pull nginx
docker pull nginx:1.24        # 指定版本
docker pull node:18-alpine    # 推荐使用 alpine 版本(更小)

# 查看本地镜像
docker images

# 删除镜像
docker rmi nginx
docker image prune            # 清理无用镜像

私有镜像仓库

bash
# 登录仓库
docker login                          # Docker Hub(默认)
docker login registry.example.com     # 私有仓库

# 登出
docker logout
docker logout registry.example.com

# 打标签(准备推送到私有仓库)
docker tag my-app:latest registry.example.com/my-app:latest
docker tag my-app:latest registry.example.com/my-app:1.0

# 推送镜像
docker push registry.example.com/my-app:latest

# 拉取私有仓库镜像
docker pull registry.example.com/my-app:1.0

# 完整工作流
docker build -t my-app:1.0 .
docker tag my-app:1.0 registry.example.com/my-app:1.0
docker push registry.example.com/my-app:1.0
常见仓库地址格式
Docker Hubusername/image:tag
GitHub Container Registryghcr.io/username/image:tag
阿里云容器镜像服务registry.cn-hangzhou.aliyuncs.com/namespace/image:tag
Harborharbor.example.com/project/image:tag

💡 推送前必须先 docker login,否则会报 unauthorized 错误。CI/CD 中可通过 echo $PASSWORD | docker login -u $USER --password-stdin 安全登录。

容器操作

创建 & 启动

bash
# 创建并启动容器
docker run nginx

# 后台运行
docker run -d nginx

# 指定名称
docker run -d --name my-nginx nginx

# 端口映射(宿主机:容器)
docker run -d -p 8080:80 nginx

# 环境变量
docker run -d -e NODE_ENV=production node-app

# 挂载目录(宿主机:容器)
docker run -d -v /host/path:/container/path nginx

# 重启策略
docker run -d --restart=always nginx

# 临时容器(退出后自动删除,适合一次性任务)
docker run --rm nginx echo "hello"

管理容器

bash
# 查看运行中的容器
docker ps

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

# 停止容器
docker stop my-nginx

# 启动已停止的容器
docker start my-nginx

# 重启容器
docker restart my-nginx

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

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

进入容器

bash
# 进入容器(交互模式)
# -i:保持 STDIN 打开(允许输入)
# -t:分配伪终端(获得命令行提示符)
# 通常一起使用 -it 才能正常交互
docker exec -it my-nginx /bin/bash
docker exec -it my-nginx sh    # Alpine 镜像用 sh

# 在容器内执行命令
docker exec my-nginx ls /app

# 连接到容器主进程(共享 stdin/stdout)
docker attach my-nginx

docker attach vs exec 区别

对比docker attachdocker exec
连接目标容器主进程(PID 1)可在容器内启动新进程
退出影响退出可能导致容器停止退出不影响容器运行
多次使用只能 attach 一次可同时多个 exec 会话
适用场景调试主进程、查看输出执行命令、进入 shell
推荐度⚠️ 谨慎使用✅ 推荐使用

💡 推荐使用 docker exec 进入容器,docker attach 退出时可能意外停止容器。如需安全 detach,使用 Ctrl+P, Ctrl+Q

日志查看

bash
# 查看日志
docker logs my-nginx

# 实时追踪
docker logs -f my-nginx

# 最近 100 行
docker logs --tail 100 my-nginx

# 带时间戳
docker logs -t my-nginx

Dockerfile

基本结构

dockerfile
# 基础镜像
FROM node:18-alpine

# 工作目录
WORKDIR /app

# 复制依赖文件
COPY package.json yarn.lock ./

# 安装依赖
RUN yarn install --production

# 复制源代码
COPY . .

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["node", "app.js"]

常用指令

dockerfile
# 设置环境变量
ENV NODE_ENV=production

# 添加元数据
LABEL maintainer="[email protected]"

# 构建时执行
RUN apt-get update && apt-get install -y curl

# 复制文件(推荐)
COPY . .
COPY --from=builder /app/dist ./dist

# 多阶段构建
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN yarn build

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

指令详解

FROM

指定基础镜像,必须是 Dockerfile 的第一条指令。

dockerfile
FROM node:18-alpine
FROM nginx:1.24-alpine
FROM python:3.11-slim
FROM scratch    # 空白镜像,用于构建最小镜像
语法说明
FROM <image>默认 latest 标签
FROM <image>:<tag>指定版本标签
FROM <image> AS <name>命名构建阶段(多阶段构建)

WORKDIR

设置工作目录,后续指令在此目录下执行。目录不存在时会自动创建。

dockerfile
WORKDIR /app
WORKDIR /usr/src/app
WORKDIR /app    # 可多次使用,支持相对路径
WORKDIR src     # 相对于 /app,即 /app/src
要点说明
优于 RUN cdWORKDIR 对后续所有指令生效
自动创建目录不存在时自动 mkdir -p
可多次使用每次切换上下文

COPY

从构建上下文复制文件到镜像中。

dockerfile
COPY package.json ./
COPY src/ ./src/
COPY --from=builder /app/dist ./dist    # 从其他构建阶段复制
COPY --chown=node:node . ./app           # 复制时设置所有者
COPY --chmod=755 script.sh ./            # 复制时设置权限(BuildKit)
语法说明
COPY <src>... <dest>复制文件或目录
COPY --from=<stage>多阶段构建中从其他阶段复制
COPY --chown=<user>:<group>设置文件所有者
COPY --chmod=<perms>设置文件权限(需 BuildKit)

ADD

与 COPY 类似,但功能更多。

dockerfile
ADD app.tar.gz /app/    # 自动解压 tar 文件
ADD https://example.com/file.txt /app/    # 支持远程 URL
ADD --chown=node:node . ./app

COPY vs ADD 区别

特性COPYADD
基本文件复制
自动解压 tar
支持远程 URL
推荐使用✅ 优先使用仅在需要解压时使用

💡 最佳实践:优先使用 COPY,只在需要自动解压 tar 包时使用 ADD。COPY 语义更明确,行为更可预测。

RUN

在构建时执行命令,结果会保存为新的镜像层。

dockerfile
# shell 格式(通过 /bin/sh -c 执行)
RUN apt-get update && apt-get install -y curl

# exec 格式(直接执行,不经过 shell)
RUN ["node", "app.js"]

# 合并命令减少层数
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       curl \
       ca-certificates \
    && rm -rf /var/lib/apt/lists/*
要点说明
合并 RUN&& 合并多个命令,减少镜像层
清理缓存安装后删除缓存文件减小体积
--no-install-recommends避免安装非必要依赖

CMD vs ENTRYPOINT

CMD — 容器启动时的默认命令,可被 docker run 参数覆盖。

dockerfile
# exec 格式(推荐)
CMD ["node", "app.js"]

# shell 格式
CMD node app.js

# 设置默认参数(配合 ENTRYPOINT)
CMD ["--port", "3000"]

ENTRYPOINT — 容器的入口命令,不容易被覆盖。

dockerfile
# exec 格式(推荐)
ENTRYPOINT ["node", "app.js"]

# shell 格式
ENTRYPOINT node app.js

CMD 与 ENTRYPOINT 组合用法

dockerfile
ENTRYPOINT ["node"]
CMD ["app.js"]
docker run 命令实际执行
docker run my-appnode app.js
docker run my-app server.jsnode server.js(CMD 被覆盖)
docker run my-app --port 3000node --port 3000(CMD 被覆盖)
对比CMDENTRYPOINT
用途默认参数固定入口命令
docker run 参数覆盖✅ 容易覆盖❌ 需要 --entrypoint
每个 Dockerfile 数量只有最后一个生效只有最后一个生效
组合使用提供默认参数提供主命令

💡 最佳实践:用 ENTRYPOINT 定义主命令,用 CMD 定义默认参数。用户可通过 docker run 参数覆盖 CMD 部分。

EXPOSE

声明容器运行时监听的端口(仅文档作用,不会实际发布端口)。

dockerfile
EXPOSE 3000
EXPOSE 80/tcp
EXPOSE 443/tcp
EXPOSE 8080/udp

💡 EXPOSE 只是声明,实际端口映射需要在 docker run -p 或 docker-compose 的 ports 中配置。

ENV

设置环境变量,构建时和运行时都有效。

dockerfile
ENV NODE_ENV=production
ENV APP_HOME /app
ENV PATH=$APP_HOME/bin:$PATH

# 一次设置多个
ENV NODE_ENV=production \
    APP_PORT=3000 \
    APP_HOME=/app

ARG vs ENV 区别

特性ARGENV
作用阶段仅构建时构建时 + 运行时
运行时可见
docker run -e 覆盖
docker build --build-arg 传入
默认值可在 Dockerfile 中设置可在 Dockerfile 中设置
dockerfile
# ARG 仅构建时可用
ARG NODE_VERSION=18
FROM node:${NODE_VERSION}-alpine

# ENV 构建时和运行时都可用
ENV NODE_ENV=production

VOLUME

声明匿名数据卷的挂载点。

dockerfile
VOLUME /app/data
VOLUME ["/app/data", "/app/logs"]

💡 VOLUME 声明后,该路径的数据不会随镜像层保存。适合数据库数据、日志等需要持久化的目录。

USER

指定运行容器时的用户。

dockerfile
RUN groupadd -r node && useradd -r -g node node
USER node

# 或使用 UID:GID
USER 1000:1000

HEALTHCHECK

定义容器健康检查。

dockerfile
# 每 30 秒检查一次,超时 10 秒,连续 3 次失败标记为 unhealthy
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 禁用基础镜像的健康检查
HEALTHCHECK NONE
参数说明默认值
--interval检查间隔30s
--timeout超时时间30s
--start-period启动等待时间30s
--retries失败重试次数3

LABEL

为镜像添加元数据。

dockerfile
LABEL maintainer="[email protected]"
LABEL version="1.0"
LABEL description="My application"

# 多行
LABEL org.opencontainers.image.title="My App" \
      org.opencontainers.image.version="1.0" \
      org.opencontainers.image.authors="[email protected]"

SHELL

更改默认 shell。

dockerfile
# Linux 默认 ["/bin/sh", "-c"]
# Windows 默认 ["cmd", "/S", "/C"]
SHELL ["/bin/bash", "-c"]

RUN apt-get update

STOPSIGNAL

设置停止容器时发送的系统信号。

dockerfile
STOPSIGNAL SIGQUIT
STOPSIGNAL 9

ONBUILD

为将来的构建添加触发器,当该镜像作为其他镜像的基础镜像时执行。

dockerfile
ONBUILD COPY package.json ./
ONBUILD RUN yarn install

.dockerignore 文件

在构建上下文中排除不需要的文件,减小构建上下文大小、加速构建、避免敏感文件泄露。

text
# 依赖目录
node_modules
vendor

# 构建产物
dist
build
*.log

# 版本控制
.git
.gitignore

# Docker 相关
Dockerfile*
docker-compose*.yml
.dockerignore

# IDE 和编辑器
.vscode
.idea
*.swp
*.swo

# 环境变量(敏感信息)
.env
.env.*
!.env.example

# 操作系统文件
.DS_Store
Thumbs.db

# 测试和文档
test
tests
*.md
LICENSE

💡 .dockerignore 支持 glob 模式,! 前缀表示排除(即保留)。

docker init(交互式生成 Dockerfile)

docker init 可以根据项目类型自动生成 Dockerfile、.dockerignore 和 docker-compose.yml。

bash
# 在项目目录下运行,交互式选择项目类型
docker init

支持的项目模板:

模板生成内容
GoDockerfile + .dockerignore
JavaDockerfile + .dockerignore
Node.jsDockerfile + .dockerignore + compose.yaml
PythonDockerfile + .dockerignore + compose.yaml
RustDockerfile + .dockerignore
ASP.NET CoreDockerfile + .dockerignore + compose.yaml
Other基础 Dockerfile + .dockerignore

💡 生成的文件是起点而非终点,建议根据实际项目需求调整。例如添加多阶段构建、优化层缓存、配置健康检查等。

镜像优化最佳实践

多阶段构建

将构建环境和运行环境分离,最终镜像只包含运行所需的文件。

Vue/React 前端项目完整示例:

dockerfile
# ---- 构建阶段(builder)----
FROM node:18-alpine AS builder       # 使用 Node.js 18 Alpine 版本作为构建环境,AS builder 命名此阶段
WORKDIR /app                         # 设置工作目录为 /app(不存在会自动创建)
COPY package.json yarn.lock ./       # 先复制依赖声明文件(利用 Docker 层缓存,依赖不变时跳过安装)
RUN yarn install --frozen-lockfile   # 安装依赖,--frozen-lockfile 确保 lock 文件不被修改
COPY . .                             # 再复制源代码(源码变化不会触发重新安装依赖)
RUN yarn build                       # 执行构建,产物默认输出到 /app/dist

# ---- 运行阶段(最终镜像)----
FROM nginx:1.24-alpine               # 使用 nginx Alpine 版本作为最终镜像(仅 ~25MB)
COPY --from=builder /app/dist /usr/share/nginx/html  # 从 builder 阶段复制构建产物到 nginx 静态目录
COPY nginx.conf /etc/nginx/conf.d/default.conf       # 复制自定义 nginx 配置(含 SPA history 路由)
EXPOSE 80                            # 声明容器监听 80 端口
CMD ["nginx", "-g", "daemon off;"]   # 启动 nginx 并保持前台运行(容器必需)

Node.js API 项目完整示例:

dockerfile
# ---- 依赖阶段(deps):只安装生产依赖,体积最小 ----
FROM node:18-alpine AS deps          # 命名为 deps 阶段
WORKDIR /app                         # 设置工作目录
COPY package.json yarn.lock ./       # 复制依赖声明文件
RUN yarn install --frozen-lockfile --production && \  # 只安装生产依赖(不含 devDependencies)
    cp -R node_modules /prod_modules # 将生产依赖复制到单独目录,供运行阶段使用

# ---- 构建阶段(builder):安装全部依赖并编译 ----
FROM node:18-alpine AS builder       # 新的构建阶段
WORKDIR /app
COPY package.json yarn.lock ./       # 复制依赖声明文件
RUN yarn install --frozen-lockfile   # 安装全部依赖(含 devDependencies,编译可能需要)
COPY . .                             # 复制源代码
RUN yarn build                       # 编译 TypeScript 或打包

# ---- 运行阶段(最终镜像):只包含运行时必需文件 ----
FROM node:18-alpine                  # 最小的 Node.js 镜像
WORKDIR /app
COPY --from=deps /prod_modules ./node_modules  # 从 deps 阶段复制生产依赖
COPY --from=builder /app/dist ./dist           # 从 builder 阶段复制编译产物
COPY package.json ./                           # 复制 package.json(用于 scripts 等)
EXPOSE 3000                        # 声明监听端口
USER node                           # 使用非 root 用户运行(安全最佳实践)
CMD ["node", "dist/main.js"]       # 启动应用

Go 项目(最小镜像):

dockerfile
# ---- 构建阶段 ----
FROM golang:1.21-alpine AS builder   # 使用 Go 1.21 Alpine 版本
WORKDIR /app                         # 设置工作目录
COPY go.mod go.sum ./                # 先复制依赖文件
RUN go mod download                  # 下载 Go 模块依赖(利用层缓存)
COPY . .                             # 复制源代码
RUN CGO_ENABLED=0 GOOS=linux go build -o main .  # 静态编译,禁用 CGO,生成 Linux 可执行文件

# ---- 运行阶段 ----
FROM scratch                         # 空白镜像(0 字节),Go 静态编译无需任何运行时
COPY --from=builder /app/main /main  # 从 builder 阶段复制编译好的二进制文件
EXPOSE 8080                          # 声明监听端口
CMD ["/main"]                        # 运行程序

减少镜像体积的方法

方法说明
使用 Alpine 基础镜像node:18-alpinenode:18 小约 10 倍
多阶段构建构建工具不进入最终镜像
合并 RUN 指令减少镜像层数
清理包管理器缓存rm -rf /var/lib/apt/lists/*
--no-install-recommends避免安装推荐包
使用 .dockerignore排除不需要的文件
选择合适基础镜像slim 比完整版小,alpine 更小

镜像分层原理

Docker 镜像由多个只读层(layer)叠加而成,每条 Dockerfile 指令生成一层。

概念说明
只读层镜像的每一层都是只读的,多个镜像可共享相同层
容器层容器启动时在镜像顶部添加一个可写层(Copy-on-Write)
层共享基于相同基础镜像的容器共享底层,节省磁盘空间
CoW 机制修改文件时先从只读层复制到可写层再修改,不影响原始层
bash
# 查看镜像分层
docker image inspect nginx:alpine --format '{{.RootFS.Layers}}'

# 查看每层大小和命令
docker history nginx:alpine

💡 理解分层原理有助于优化 Dockerfile:将不常变化的指令放前面(利用缓存),频繁变化的放后面。

层缓存优化策略

Docker 按层缓存,某一层变化后其后所有层都会重新构建。

dockerfile
# ❌ 错误:任何文件变化都会重新安装依赖
COPY . .
RUN yarn install

# ✅ 正确:先复制依赖文件,利用缓存
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
策略说明
依赖文件先复制package.jsonyarn.lockgo.mod 等先复制并安装
频繁变化的文件后复制源代码最后复制
RUN 合并将更新和安装合并为一条 RUN
使用 --mount=type=cacheBuildKit 缓存包管理器目录(高级用法)
dockerfile
# BuildKit 缓存示例
RUN --mount=type=cache,target=/var/cache/apt \
    apt-get update && apt-get install -y curl

RUN --mount=type=cache,target=/root/.cache/yarn \
    yarn install --frozen-lockfile

Alpine vs Debian 基础镜像对比

特性AlpineDebian/Ubuntu
镜像大小~5MB~50-120MB
包管理器apkapt-get
C 库muslglibc
Shellashbash
适用场景生产环境、追求最小体积需要 glibc 兼容性
潜在问题musl 可能导致部分软件不兼容体积较大

💡 Node.js 项目推荐 node:18-alpine,Python 项目推荐 python:3.11-slim,Go 项目可用 scratchalpine

构建镜像

bash
# 构建
docker build -t my-app .
docker build -t my-app:1.0 .

# 指定 Dockerfile
docker build -f Dockerfile.prod -t my-app .

# 无缓存构建
docker build --no-cache -t my-app .

# 指定构建上下文为远程仓库
docker build -t my-app https://github.com/user/repo.git#main

# 构建时传入参数
docker build --build-arg NODE_VERSION=18 -t my-app .

# 查看构建过程(BuildKit)
DOCKER_BUILDKIT=1 docker build -t my-app .

# 构建多平台镜像
docker buildx build --platform linux/amd64,linux/arm64 -t my-app .

docker buildx(多平台构建)

buildx 是 Docker 的扩展构建工具,支持多平台镜像构建、构建缓存导入导出等高级功能。

bash
# 查看可用的构建器
docker buildx ls

# 创建专用构建器(支持多平台)
docker buildx create --name mybuilder --driver docker-container --use
docker buildx inspect --bootstrap

# 构建并推送多平台镜像
docker buildx build --platform linux/amd64,linux/arm64 \
  -t registry.example.com/my-app:latest \
  --push .

# 构建多平台镜像并加载到本地
docker buildx build --platform linux/amd64,linux/arm64 \
  -t my-app:latest \
  --load .

# 使用缓存加速构建
docker buildx build --platform linux/amd64,linux/arm64 \
  -t my-app:latest \
  --cache-from type=registry,ref=registry.example.com/my-app:cache \
  --cache-to type=registry,ref=registry.example.com/my-app:cache,mode=max \
  --push .
参数说明
--platform目标平台,多个用逗号分隔
--push构建后自动推送到仓库
--load构建后加载到本地(仅支持单平台,多平台需用 --push 或先指定单平台)
--cache-from缓存来源
--cache-to缓存输出目标
--builder指定使用的构建器

💡 多平台构建常用于同时支持 amd64(x86 服务器)和 arm64(Apple Silicon、ARM 服务器)。--load 仅支持单平台,多平台需用 --push

docker context(多环境管理)

docker context 用于管理多个 Docker 环境(本地、远程服务器、Kubernetes 等),快速切换连接目标。

bash
# 查看所有上下文
docker context ls

# 创建新上下文(连接远程 Docker)
docker context create remote-server \
  --docker "host=ssh://[email protected]" \
  --description "Production server"

# 切换上下文
docker context use remote-server

# 在指定上下文执行命令(不切换默认)
docker --context remote-server ps

# 查看当前上下文详情
docker context inspect

# 删除上下文
docker context rm remote-server
字段说明
NAME上下文名称
DESCRIPTION描述信息
DOCKER ENDPOINTDocker 引擎连接地址
ERROR连接错误信息(如有)

💡 Docker Desktop 创建的 desktop-linux / desktop-windows 上下文是本地引擎。通过 context 可免去反复设置 DOCKER_HOST 环境变量。

Docker Compose

docker-compose.yml 示例

yaml
version: '3.8'                      # Compose 文件格式版本(新版 Docker 可省略此行)

services:
  app:                              # 应用服务
    build: .                        # 从当前目录的 Dockerfile 构建镜像
    ports:
      - "3000:3000"                 # 端口映射:宿主机 3000 → 容器 3000
    environment:
      - NODE_ENV=production         # 环境变量:运行模式
      - DB_HOST=db                  # 环境变量:数据库主机名(用服务名即可,Compose 自动解析)
    depends_on:
      - db                          # 依赖 db 服务,db 启动后才启动 app
    restart: always                 # 重启策略:容器退出后总是重启

  db:                               # 数据库服务
    image: postgres:15              # 使用 postgres 15 官方镜像
    environment:
      - POSTGRES_PASSWORD=secret    # 数据库密码
      - POSTGRES_DB=myapp           # 自动创建的数据库名
    volumes:
      - postgres_data:/var/lib/postgresql/data  # 命名数据卷:持久化数据库文件
    ports:
      - "5432:5432"                 # 端口映射:宿主机 5432 → 容器 5432

volumes:
  postgres_data:                    # 声明命名数据卷(删除容器后数据不丢失)

完整配置字段说明

顶层字段

字段说明
versionCompose 文件格式版本('3.8' 等,新版可省略)
services定义各个服务
volumes定义命名数据卷
networks定义自定义网络
configs定义配置对象
secrets定义敏感数据

services 下的常用字段

image / build

yaml
services:
  # 直接使用镜像
  web:
    image: nginx:1.24-alpine

  # 从 Dockerfile 构建
  app:
    build:
      context: .
      dockerfile: Dockerfile.prod
      args:
        NODE_VERSION: 18
      target: builder    # 多阶段构建指定目标阶段

ports / expose

yaml
services:
  app:
    ports:
      - "3000:3000"         # 宿主机:容器
      - "8080:80"
      - "127.0.0.1:3000:3000"    # 仅本地访问
      - "9090-9091:8080-8081"    # 端口范围
    expose:
      - "3000"    # 仅对其他服务暴露,不映射到宿主机

environment / env_file

yaml
services:
  # 列表格式
  app:
    environment:
      - NODE_ENV=production
      - DB_HOST=db

  # 映射格式
  api:
    environment:
      NODE_ENV: production
      DB_HOST: db

  # 从文件读取
  web:
    env_file:
      - .env
      - .env.production

  # 指定文件路径和必填项(Compose v2.24+)
  worker:
    env_file:
      - path: .env
        required: true
      - path: .env.local
        required: false

volumes

yaml
services:
  app:
    volumes:
      - ./src:/app/src              # 绑定挂载
      - node_modules:/app/node_modules    # 命名数据卷
      - /app/logs                   # 匿名数据卷
      - type: bind
        source: ./config
        target: /app/config
        read_only: true

depends_on

yaml
services:
  app:
    depends_on:
      db:
        condition: service_healthy    # 等待健康检查通过
      redis:
        condition: service_started    # 等待启动完成
条件说明
service_started服务已启动(默认)
service_healthy服务健康检查通过
service_completed_successfully服务成功退出

healthcheck

yaml
services:
  # 健康检查配置
  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # 禁用健康检查
  worker:
    healthcheck:
      disable: true

restart

yaml
services:
  app:
    restart: always        # 总是重启

  api:
    restart: on-failure    # 仅失败时重启

  web:
    restart: unless-stopped    # 除非手动停止

  db:
    restart: "no"          # 不自动重启(默认)

networks

yaml
services:
  app:
    networks:
      - frontend
      backend:
        aliases:
          - app.local    # 网络别名
        ipv4_address: 172.20.0.10    # 固定 IP

labels

yaml
services:
  app:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"

logging

yaml
services:
  # JSON 文件日志(默认)
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  # syslog 日志
  api:
    logging:
      driver: syslog
      options:
        syslog-address: "tcp://192.168.0.1:514"
驱动说明
json-file默认,JSON 格式日志文件
syslog发送到 syslog
journald发送到 journald
none禁用日志

deploy

yaml
services:
  app:
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "0.50"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s

多环境配置

方式一:docker-compose.override.yml

默认加载 docker-compose.yml + docker-compose.override.yml,适合开发环境覆盖配置。

yaml
# docker-compose.yml(基础配置)
services:
  app:
    build: .
    environment:
      - NODE_ENV=production
yaml
# docker-compose.override.yml(开发环境覆盖,自动加载)
services:
  app:
    volumes:
      - ./src:/app/src    # 开发时挂载源码
    environment:
      - NODE_ENV=development
    ports:
      - "3000:3000"
yaml
# docker-compose.prod.yml(生产环境配置)
services:
  app:
    restart: always
    deploy:
      resources:
        limits:
          memory: 512M

方式二:-f 指定多个文件

bash
# 开发环境(默认加载 override)
docker compose up -d

# 生产环境
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 测试环境
docker compose -f docker-compose.yml -f docker-compose.test.yml up -d

方式三:.env 文件自动变量替换

docker-compose.yml 中可直接引用 .env 文件中的变量,Compose 启动时自动读取。

bash
# .env 文件(放在 docker-compose.yml 同级目录)
POSTGRES_PASSWORD=secret123
APP_PORT=3000
DB_NAME=myapp
yaml
# docker-compose.yml 中使用 ${VAR} 引用
services:
  app:
    ports:
      - "${APP_PORT}:3000"
    environment:
      - DB_NAME=${DB_NAME}

  db:
    image: postgres:15
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
特性.env 文件env_file 字段
变量替换替换 compose 文件中的 ${VAR}注入到容器环境变量
读取方式Compose 自动读取同级 .env需显式声明 env_file: .env
优先级较低(可被 shell 环境变量覆盖)较高
文件名固定为 .env可指定任意文件名

💡 .env 文件用于 compose 文件中的变量替换,env_file 字段用于注入容器环境变量,两者用途不同。.env 文件不要提交到版本库,应加入 .gitignore

Profiles

按需启动服务组,避免维护多个 Compose 文件。

yaml
services:
  app:
    image: node:18-alpine
    # 无 profiles,始终启动

  db:
    image: postgres:15
    profiles:
      - dev
      - prod

  debug-tools:
    image: busybox
    profiles:
      - dev    # 仅开发环境

  monitoring:
    image: prometheus
    profiles:
      - prod    # 仅生产环境
bash
# 启动默认服务(无 profiles 的服务)
docker compose up -d

# 启动 dev 相关服务
docker compose --profile dev up -d

# 启动多个 profiles
docker compose --profile dev --profile monitoring up -d

# 启动所有服务
docker compose --profile dev --profile prod up -d

常用命令

bash
# 启动所有服务
docker compose up -d

# 停止所有服务
docker compose down

# 停止并删除数据卷
docker compose down -v

# 查看日志
docker compose logs -f
docker compose logs -f app    # 指定服务

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

# 查看服务状态
docker compose ps

# 进入某个服务
docker compose exec app sh

# 查看服务配置(解析后)
docker compose config

# 扩展服务副本数
docker compose up -d --scale app=3

# 只启动指定服务
docker compose up -d app db

# 查看服务端口映射
docker compose port app 3000

# 暂停/恢复服务
docker compose pause
docker compose unpause

数据卷

bash
# 创建数据卷
docker volume create my-vol

# 查看数据卷
docker volume ls

# 使用数据卷
docker run -d -v my-vol:/app/data nginx

# 删除数据卷
docker volume rm my-vol

# 清理无用数据卷
docker volume prune

网络

基本操作

bash
# 查看网络
docker network ls

# 创建网络
docker network create my-net

# 使用网络
docker run -d --network my-net --name app1 node-app
docker run -d --network my-net --name app2 node-app

# 容器间可通过名称访问
# app1 中可以直接访问 app2:port

网络模式

模式说明适用场景
bridge默认模式,容器通过虚拟网桥通信单机多容器通信
host容器直接使用宿主机网络栈高性能网络、端口冲突不敏感
none无网络连接安全隔离场景
overlay跨主机容器通信Docker Swarm 集群
macvlan容器拥有独立 MAC 地址需要容器在物理网络中可见

bridge(默认)

bash
# 使用默认 bridge 网络
docker run -d --name app nginx

# 使用自定义 bridge 网络(推荐)
docker network create my-net
docker run -d --network my-net --name app1 nginx
docker run -d --network my-net --name app2 nginx

💡 自定义 bridge 网络支持自动 DNS 解析(容器间可用名称访问),默认 bridge 网络不支持。

host

bash
# 容器直接使用宿主机网络,无需 -p 端口映射
docker run -d --network host nginx

⚠️ host 模式下容器与宿主机共享网络命名空间,端口会冲突。仅支持 Linux。

none

bash
# 完全禁用网络
docker run -d --network none alpine

overlay

bash
# 需要先初始化 Swarm
docker swarm init

# 创建 overlay 网络
docker network create -d overlay my-overlay

# 在 Swarm 服务中使用
docker service create --network my-overlay --name web nginx

macvlan

bash
# 创建 macvlan 网络
docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  my-macvlan

docker run -d --network my-macvlan --ip 192.168.1.100 --name app nginx

自定义网络

bash
# 创建自定义 bridge 网络
docker network create \
  --driver bridge \
  --subnet 172.20.0.0/16 \
  --ip-range 172.20.240.0/20 \
  --gateway 172.20.0.1 \
  my-net

# 查看网络详情
docker network inspect my-net

# 将容器连接到网络
docker network connect my-net my-container

# 将容器连接到网络并指定 IP
docker network connect --ip 172.20.0.100 my-net my-container

# 断开网络
docker network disconnect my-net my-container

# 删除网络
docker network rm my-net

# 清理无用网络
docker network prune

容器间通信

bash
# 创建自定义网络
docker network create app-net

# 启动数据库
docker run -d --network app-net --name db postgres:15

# 启动应用,通过容器名访问数据库
docker run -d --network app-net \
  -e DB_HOST=db \
  -e DB_PORT=5432 \
  --name app \
  my-node-app

💡 在同一自定义网络中,容器间可直接使用容器名作为主机名进行通信,无需知道对方 IP。

Docker 安全最佳实践

非 root 用户运行

dockerfile
FROM node:18-alpine

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

WORKDIR /app
COPY --chown=appuser:appgroup . .

# 切换到非 root 用户
USER appuser

CMD ["node", "app.js"]
bash
# 运行时指定用户
docker run --user 1000:1000 my-app

# 只读文件系统
docker run --read-only --tmpfs /tmp my-app

只读文件系统

bash
# 只读根文件系统 + 临时可写目录
docker run --read-only \
  --tmpfs /tmp:rw,noexec,nosuid \
  --tmpfs /app/cache:rw,noexec,nosuid \
  my-app
yaml
# Docker Compose 中配置
services:
  app:
    read_only: true
    tmpfs:
      - /tmp
      - /app/cache

资源限制

bash
# 限制内存
docker run -m 512m my-app
docker run --memory=1g --memory-swap=2g my-app

# 限制 CPU
docker run --cpus=1.5 my-app
docker run --cpu-shares=512 my-app    # 相对权重

# 限制内存 + CPU
docker run -m 512m --cpus=1.0 my-app
参数说明
-m / --memory最大内存
--memory-swap内存 + 交换空间总量
--cpus可用 CPU 核心数
--cpu-sharesCPU 相对权重(默认 1024)
--pids-limit限制进程数(防止 fork 炸弹)
yaml
# Docker Compose 中配置资源限制
services:
  app:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
          pids: 100
        reservations:
          cpus: "0.25"
          memory: 256M

镜像安全扫描

bash
# 使用 Docker Scout 扫描(Docker Desktop 内置)
docker scout cves my-app:latest

# 使用 Trivy 扫描
trivy image my-app:latest

# 扫描并仅显示高危漏洞
trivy image --severity HIGH,CRITICAL my-app:latest

# 扫描 Dockerfile
trivy config Dockerfile

其他安全建议

实践说明
使用官方镜像优先选择 Docker Hub 官方镜像
固定镜像版本避免使用 latest,使用具体版本号
不存储密钥在镜像中使用环境变量或 Docker Secrets
定期更新基础镜像修复已知安全漏洞
最小化安装只安装必要的包和工具
使用 --no-new-privileges防止容器内提权
禁用 --privileged避免授予容器所有权限
bash
# 禁止提权
docker run --security-opt=no-new-privileges my-app

# 使用 seccomp 限制系统调用
docker run --security-opt seccomp=custom-profile.json my-app

前端项目 Docker 化完整示例

Vue/React 项目多阶段构建

Dockerfile:

dockerfile
# ---- 构建阶段 ----
FROM node:18-alpine AS builder       # 使用 Node.js 18 Alpine 作为构建环境
WORKDIR /app                         # 设置工作目录

COPY package.json yarn.lock ./       # 先复制依赖文件(利用层缓存)
RUN yarn install --frozen-lockfile   # 安装依赖,frozen-lockfile 确保版本一致

COPY . .                             # 复制源代码
RUN yarn build                       # 执行构建

# ---- 运行阶段 ----
FROM nginx:1.24-alpine               # 使用 nginx Alpine 版本作为运行环境

# 自定义 nginx 配置(包含 Vue SPA history 路由)
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 复制构建产物到 nginx 静态目录
COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80                            # 声明 80 端口
CMD ["nginx", "-g", "daemon off;"]   # 前台启动 nginx

nginx.conf(Vue Router history 模式):

nginx
server {
    listen 80;                                    # 监听 80 端口
    server_name localhost;                        # 服务器域名
    root /usr/share/nginx/html;                   # 静态文件根目录(对应构建产物)
    index index.html;                             # 默认首页文件

    # Vue/React SPA history 路由:所有未匹配的请求回退到 index.html
    # 这样前端路由(如 /about、/user/123)才能正常工作
    location / {
        try_files $uri $uri/ /index.html;         # 先找文件 → 再找目录 → 都没有就返回 index.html
    }

    # API 反向代理:将 /api/ 开头的请求转发给后端
    location /api/ {
        proxy_pass http://backend:3000/;          # 转发到后端服务(去掉 /api 前缀)
        proxy_set_header Host $host;              # 传递原始 Host 头
        proxy_set_header X-Real-IP $remote_addr;  # 传递客户端真实 IP
    }

    # 静态资源长期缓存:文件名带 hash,内容变化时文件名也变
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;                               # 缓存 1 年
        add_header Cache-Control "public, immutable";  # 公共缓存,不可变
    }
}

Node.js API 项目

Dockerfile:

dockerfile
# ---- 构建阶段 ----
FROM node:18-alpine AS builder       # 使用 Node.js 18 Alpine 作为构建环境
WORKDIR /app                         # 设置工作目录

COPY package.json yarn.lock ./       # 先复制依赖声明文件(利用层缓存)
RUN yarn install --frozen-lockfile   # 安装全部依赖(含 devDependencies)

COPY . .                             # 复制源代码
RUN yarn build                       # 编译 TypeScript 或打包

# ---- 运行阶段 ----
FROM node:18-alpine                  # 使用 Node.js 18 Alpine 作为运行环境
WORKDIR /app                         # 设置工作目录

# 创建非 root 用户(安全最佳实践,避免容器以 root 身份运行)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 从构建阶段复制文件,并设置文件所有者为 appuser
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./

ENV NODE_ENV=production              # 设置生产环境变量
EXPOSE 3000                          # 声明监听 3000 端口

USER appuser                         # 切换到非 root 用户运行
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
  # 健康检查:每 30 秒检测一次 /health 端口,失败 3 次标记为不健康

CMD ["node", "dist/main.js"]         # 启动应用

docker-compose.yml 编排前后端 + 数据库

yaml
version: '3.8'                                  # Compose 文件格式版本

services:
  frontend:                                     # 前端服务(Vue/React)
    build:
      context: ./frontend                       # 构建上下文为 frontend 目录
      dockerfile: Dockerfile                    # 使用该目录下的 Dockerfile
    ports:
      - "80:80"                                 # 对外暴露 80 端口(HTTP)
    depends_on:
      - backend                                 # 依赖后端服务
    restart: unless-stopped                     # 除非手动停止,否则总是重启

  backend:                                      # 后端服务(Node.js API)
    build:
      context: ./backend                        # 构建上下文为 backend 目录
      dockerfile: Dockerfile
    ports:
      - "3000:3000"                             # 对外暴露 3000 端口
    environment:
      - NODE_ENV=production                     # 生产环境
      - DB_HOST=postgres                        # 数据库地址(用服务名)
      - DB_PORT=5432                            # 数据库端口
      - DB_NAME=myapp                           # 数据库名
      - DB_USER=admin                           # 数据库用户
      - DB_PASSWORD_FILE=/run/secrets/db_password  # 从 Docker Secret 读取密码(更安全)
    depends_on:
      postgres:
        condition: service_healthy              # 等待数据库健康检查通过后再启动
      redis:
        condition: service_started              # 等待 Redis 启动完成
    secrets:
      - db_password                             # 挂载 Docker Secret
    volumes:
      - uploads:/app/uploads                    # 命名数据卷:持久化上传文件
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M                          # 最大内存限制
          cpus: "1.0"                           # 最大 CPU 核心数

  postgres:                                     # PostgreSQL 数据库
    image: postgres:15-alpine                   # 使用 PostgreSQL 15 Alpine 版本
    environment:
      - POSTGRES_DB=myapp                       # 自动创建的数据库
      - POSTGRES_USER=admin                     # 数据库超级用户
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password  # 从 Secret 读取密码
    secrets:
      - db_password                             # 挂载 Docker Secret
    volumes:
      - postgres_data:/var/lib/postgresql/data  # 持久化数据库文件
    ports:
      - "5432:5432"                             # 端口映射
    healthcheck:                                # 健康检查:确保数据库就绪后再启动后端
      test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]  # 检查数据库是否可连接
      interval: 10s                             # 每 10 秒检查一次
      timeout: 5s                               # 超时时间
      retries: 5                                # 重试次数
    restart: unless-stopped

  redis:                                        # Redis 缓存
    image: redis:7-alpine                       # 使用 Redis 7 Alpine 版本
    volumes:
      - redis_data:/data                        # 持久化 Redis 数据
    restart: unless-stopped

volumes:                                        # 声明命名数据卷
  postgres_data:                                # PostgreSQL 数据
  redis_data:                                   # Redis 数据
  uploads:                                      # 用户上传文件

secrets:                                        # 声明 Docker Secret(比环境变量更安全)
  db_password:
    file: ./secrets/db_password.txt             # 从本地文件读取密码(不要提交到 Git)

常用技巧

查看磁盘占用

bash
docker system df
docker system df -v    # 详细信息

实时资源监控

bash
# 查看所有容器的资源使用
docker stats

# 单次输出(不实时刷新)
docker stats --no-stream

# 指定容器
docker stats my-app my-db
说明
CPU %CPU 使用率
MEM USAGE / LIMIT内存使用 / 限制
MEM %内存使用率
NET I/O网络输入/输出
BLOCK I/O磁盘输入/输出
PIDS进程数

查看详细信息

bash
# 查看容器详细信息
docker inspect my-app

# 获取特定字段(Go 模板)
docker inspect --format='{{.NetworkSettings.IPAddress}}' my-app
docker inspect --format='{{.State.Status}}' my-app
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-app

# 查看镜像详细信息
docker inspect nginx:alpine

导出导入镜像

bash
# 导出镜像为 tar 文件
docker save -o nginx.tar nginx:alpine
docker save nginx:alpine | gzip > nginx.tar.gz

# 导入镜像
docker load -i nginx.tar
docker load -i nginx.tar.gz    # 支持 gzip

# 导出/导入容器(含运行状态)
docker export my-container > container.tar
docker import container.tar my-image:latest
命令用途保留信息
docker save/load镜像导出/导入镜像层、标签、元数据
docker export/import容器导出/导入仅文件系统,丢失元数据

容器内文件拷贝

bash
# 从容器复制到宿主机
docker cp my-app:/app/logs/app.log ./

# 从宿主机复制到容器
docker cp ./config.yml my-app:/app/config.yml

# 复制目录
docker cp my-app:/app/data ./backup/

# 从已停止的容器复制
docker cp stopped-container:/app/data ./backup/

其他实用命令

bash
# 查看容器内进程
docker top my-app

# 查看容器资源使用
docker stats my-app --no-stream

# 查看镜像构建历史(每层的命令)
docker history my-app

# 重命名容器
docker rename old-name new-name

# 查看 Docker 版本详情
docker version
docker info

# 清理所有未使用资源
docker system prune -a --volumes

⚠️ docker system prune -a 会删除所有未使用的镜像、停止的容器和未挂载的网络。未命名卷(anonymous volumes)默认不会被删除,需加 --volumes 参数。已命名卷(named volumes)永远不会被自动删除。

查看容器文件变更(docker diff)

bash
# 查看容器内文件系统相对于镜像的变更
docker diff my-app
标记含义
A新增的文件或目录(Added)
C修改的文件或目录(Changed)
D删除的文件或目录(Deleted)
bash
# 常见用法:检查容器是否被意外修改
docker diff my-app
# 输出示例:
# C /var/run
# A /app/data/cache.tmp
# C /etc/nginx/nginx.conf

💡 docker diff 只显示容器可写层的变更,不显示镜像只读层的内容。适合排查容器运行时产生的文件变化。

从容器创建镜像(docker commit)

bash
# 从运行中的容器创建镜像
docker commit my-app my-app-snapshot

# 带标签
docker commit my-app my-app:debug-v1

# 添加提交信息和元数据
docker commit -a "author" -m "added debug tools" my-app my-app:debug

# 修改启动命令
docker commit --change='CMD ["node", "debug.js"]' my-app my-app:debug

⚠️ docker commit 不推荐用于生产环境,因为它无法追溯构建过程、不利于版本管理。应优先使用 Dockerfile 构建镜像。典型用途是临时保存调试状态。

容器无法启动

bash
# 查看容器日志
docker logs my-app

# 查看容器详情
docker inspect my-app

镜像过大

bash
# 使用多阶段构建
# 使用 alpine 基础镜像
# 合并 RUN 指令减少层数
# 使用 .dockerignore 排除不必要文件

清理磁盘空间

bash
# 清理所有未使用的资源(镜像、容器、网络、数据卷)
docker system prune -a

# 只清理镜像
docker image prune -a

# 查看磁盘使用
docker system df

参考

个人学习笔记,部分内容借助 AI 辅助整理,仅供查阅参考,请以官方文档为准