前言
Docker 是一个容器化平台1Docker 由 Solomon Hykes 于 2013 年创建,现已成为容器技术的事实标准。,让开发者可以把应用及其依赖打包到一个轻量级、可移植的容器中,然后运行在任何 Linux 机器上。相比传统虚拟机,Docker 容器共享主机内核2容器共享宿主机内核,因此比虚拟机更轻量。这也是为什么 Linux 容器无法直接在 Windows 上运行的原因。,启动快、资源占用小。
Docker 简化了”在我机器上能跑”的问题——把整个环境做成镜像,哪里都能跑。
核心概念
镜像 vs 容器 vs 仓库
| 概念 | 类比 | 说明 |
|---|---|---|
| 镜像(Image) | 类 / 安装包 | 一个只读的模板,包含运行应用所需的文件系统和配置 |
| 容器(Container) | 实例 / 运行中的程序 | 镜像的运行实例,可读可写,可启动、停止、删除 |
| 仓库(Registry) | GitHub / 应用商店 | 存储和分发镜像的地方(Docker Hub、私有仓库) |
# 从镜像启动容器docker run nginx:latest
# 从 Dockerfile 构建镜像docker build -t my-app:1.0 .
# 推送镜像到仓库docker push my-app:1.0Docker 工作流程
关键路径:
- 开发阶段:Dockerfile → 构建镜像 → 本地测试容器
- 部署阶段:推送镜像到仓库 → 服务器拉取 → 启动容器
镜像是只读的,容器启动时会在镜像层之上加一个可写层(Container Layer)。
所有修改都在容器层,删除容器后修改丢失。要持久化数据,必须使用 volume(数据卷)。
镜像分层
Docker 镜像由多个只读层(Layer)堆叠而成,每层对应 Dockerfile 中的一条指令。
分层的好处:层可以被缓存和复用1Docker 由 Solomon Hykes 于 2013 年创建,现已成为容器技术的事实标准。。修改 Dockerfile 时,只有修改行之后的层需要重新构建,未修改的层直接用缓存,大幅加速构建。
安装与基础配置
# macOS(Homebrew)brew install --cask docker
# 验证docker --versiondocker compose version
# 国内镜像加速(Docker Desktop → Settings → Docker Engine){ "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://hub-mirror.c.163.com" ]}常用命令
镜像管理
| 命令 | 说明 |
|---|---|
docker images | 列出本地镜像 |
docker pull nginx:alpine | 拉取镜像 |
docker rmi nginx:alpine | 删除镜像 |
docker build -t my-app:1.0 . | 从 Dockerfile 构建镜像 |
docker tag my-app:1.0 user/my-app:latest | 给镜像打标签 |
docker push user/my-app:latest | 推送到仓库 |
docker save -o backup.tar my-app:1.0 | 导出镜像到文件 |
docker load -i backup.tar | 从文件导入镜像 |
容器生命周期
| 命令 | 说明 |
|---|---|
docker run nginx | 启动新容器(前台运行) |
docker run -d nginx | 后台运行(detach) |
docker run --name my-nginx nginx | 指定容器名 |
docker run -p 8080:80 nginx | 端口映射 主机:容器 |
docker ps | 列出运行中的容器 |
docker ps -a | 列出所有容器(含已停止的) |
docker stop <container> | 停止容器(发送 SIGTERM) |
docker kill <container> | 强制停止(发送 SIGKILL) |
docker start <container> | 启动已停止的容器 |
docker restart <container> | 重启容器 |
docker rm <container> | 删除容器 |
docker rm -f <container> | 强制删除运行中的容器 |
docker container prune | 清理所有已停止的容器 |
进入容器与执行命令
# 在运行中的容器内执行命令docker exec <container> ls -la
# 进入交互式 Shelldocker exec -it <container> /bin/sh # 多数 Alpine 镜像docker exec -it <container> /bin/bash # Ubuntu/Debian 类
# 查看容器日志docker logs <container>docker logs -f <container> # 持续跟踪docker logs --tail 100 <container> # 只看最后 100 行Dockerfile
Dockerfile 是一个文本文件,定义了如何构建镜像。
Dockerfile 常用指令
| 指令 | 作用 | 示例 |
|---|---|---|
FROM | 指定基础镜像 | FROM node:20-alpine |
WORKDIR | 设置工作目录 | WORKDIR /app |
COPY | 复制文件到镜像 | COPY package.json ./ |
ADD | 复制文件(支持 URL、自动解压 tar) | ADD archive.tar.gz /tmp/ |
RUN | 构建时执行命令 | RUN npm install |
CMD | 容器启动时的默认命令 | CMD ["node", "app.js"] |
ENTRYPOINT | 容器启动入口(不可被覆盖) | ENTRYPOINT ["nginx", "-g"] |
ENV | 设置环境变量 | ENV NODE_ENV=production |
EXPOSE | 声明容器监听的端口 | EXPOSE 3000 |
ARG | 构建参数(仅在构建时可用) | ARG VERSION=1.0 |
VOLUME | 声明挂载点 | VOLUME /data |
USER | 指定运行用户 | USER node |
LABEL | 添加元数据 | LABEL version="1.0" |
CMD vs ENTRYPOINT
| CMD | ENTRYPOINT | |
|---|---|---|
| 是否可被覆盖 | docker run 传参可覆盖 | 不可覆盖,除非 --entrypoint |
| 用途 | 指定默认命令和参数 | 容器”主程序”入口 |
| 组合使用 | CMD 作为 ENTRYPOINT 的默认参数 | 固定入口程序 |
# CMD 示例:默认执行 node app.js,可覆盖CMD ["node", "app.js"]# docker run my-image node server.js → 覆盖
# ENTRYPOINT 示例:固定为 pingENTRYPOINT ["ping"]CMD ["localhost"]# docker run my-image → ping localhost# docker run my-image google.com → ping google.com多阶段构建
多阶段构建可以极大减小最终镜像体积:一个阶段用来编译/构建,另一个阶段只复制最终产物。
# 第一阶段:构建FROM golang:1.21 AS builderWORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 go build -o server
# 第二阶段:运行(极小的 scratch 镜像)FROM scratchWORKDIR /COPY --from=builder /app/server /serverEXPOSE 8080CMD ["/server"]| 对比 | 单阶段 | 多阶段 |
|---|---|---|
| 镜像大小 | ~800MB(含 Go 工具链) | ~10MB(只有二进制文件) |
| 安全性 | 包含不需要的工具 | 攻击面更小 |
| 构建速度 | 几乎一致 | 一致 |
镜像体积实测对比
| 项目类型 | 单阶段 | 多阶段 | 缩减比例 |
|---|---|---|---|
| Go API | 842 MB | 12 MB | 98.6% ↓ |
| Node.js App | 1.2 GB | 180 MB | 85% ↓ |
| React 前端 | 380 MB | 45 MB | 88% ↓ |
部署提速:12MB 镜像通过 100Mbps 网络传输只需 1 秒,而 842MB 需要 67 秒。容器启动时间从 8 秒降到 0.3 秒。
Node.js 项目 Dockerfile 示例
# ===== 生产环境 =====FROM node:20-alpine AS production
# 安全:使用非 root 用户RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# 先复制依赖文件(利用 Docker 缓存层)COPY package.json pnpm-lock.yaml ./RUN npm config set registry https://registry.npmmirror.com && \ npm install --production
# 再复制源码COPY . .
USER appuserEXPOSE 3000CMD ["node", "app.js"]Docker Compose
Docker Compose 用 YAML 文件定义和管理多容器应用。
基本结构
# docker-compose.ymlversion: '3.8'
services: web: build: . ports: - "3000:3000" depends_on: - db environment: - DB_HOST=db - DB_PORT=5432
db: image: postgres:16-alpine volumes: - pgdata:/var/lib/postgresql/data environment: POSTGRES_DB: myapp POSTGRES_PASSWORD: secret
volumes: pgdata:Compose 文件常用字段
| 字段 | 说明 |
|---|---|
services | 定义各个容器服务 |
build | 指定 Dockerfile 路径 |
image | 使用现有镜像 |
ports | 端口映射 "主机:容器" |
volumes | 数据卷挂载 |
environment | 环境变量 |
depends_on | 启动依赖顺序 |
restart | 重启策略:no / always / unless-stopped / on-failure |
networks | 网络配置 |
command | 覆盖容器默认命令 |
healthcheck | 健康检查 |
Compose 常用命令
# 启动所有服务(-d 后台运行)docker compose up -d
# 重新构建并启动docker compose up -d --build
# 查看日志docker compose logs -f
# 查看运行状态docker compose ps
# 执行命令docker compose exec web node app.js
# 停止服务docker compose down
# 停止并删除数据卷(注意:数据会丢失)docker compose down -v
# 重新创建容器docker compose up -d --force-recreate不同环境的 Compose 配置
:::tabs == 开发环境
# docker-compose.dev.ymlversion: '3.8'
services: web: build: context: . target: development volumes: - .:/app # 源码热重载 - /app/node_modules # 屏蔽宿主机 node_modules ports: - "3000:3000" environment: - NODE_ENV=development - DEBUG=app:* # 开启调试日志
redis: image: redis:7-alpine ports: - "6379:6379" # 暴露端口,方便 Redis 客户端连接
db: image: postgres:16-alpine volumes: - pgdata:/var/lib/postgresql/data environment: POSTGRES_USER: dev POSTGRES_PASSWORD: dev POSTGRES_DB: myapp_dev ports: - "5432:5432" # 暴露端口,方便 GUI 工具连接
volumes: pgdata:启动:docker compose -f docker-compose.dev.yml up -d
== 生产环境
# docker-compose.prod.ymlversion: '3.8'
services: web: image: myapp:latest restart: unless-stopped environment: - NODE_ENV=production - DB_HOST=db - REDIS_HOST=redis # 不暴露端口,通过 Nginx 反向代理 networks: - backend
redis: image: redis:7-alpine restart: unless-stopped volumes: - redis_data:/data # 不暴露端口,仅内部通信 networks: - backend
db: image: postgres:16-alpine restart: unless-stopped volumes: - pgdata:/var/lib/postgresql/data environment: POSTGRES_USER: ${DB_USER} # 从 .env 读取 POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_NAME} # 不暴露端口 networks: - backend
nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - web networks: - backend
volumes: pgdata: redis_data:
networks: backend:启动:docker compose -f docker-compose.prod.yml up -d
== 测试环境
# docker-compose.test.ymlversion: '3.8'
services: web: build: context: . target: test environment: - NODE_ENV=test - CI=true depends_on: - db - redis command: npm run test:e2e
db: image: postgres:16-alpine environment: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: test_db tmpfs: - /var/lib/postgresql/data # 临时文件系统,测试结束即销毁
redis: image: redis:7-alpine用途:CI/CD 流水线中的自动化测试
:::
关键差异:
- 开发环境:暴露所有端口、挂载源码、详细日志
- 生产环境:隐藏内部端口、环境变量保密、健康检查
- 测试环境:使用临时存储、独立数据库、自动销毁
数据持久化(Volume)
容器删除后,容器内产生的数据也会消失。Volume 是 Docker 推荐的数据持久化方式。
三种挂载方式
| 方式 | 位置 | 由谁管理 | 用途 |
|---|---|---|---|
| Volume | /var/lib/docker/volumes/ | Docker 管理 | ✅ 推荐:数据库、持久化数据 |
| Bind Mount | 宿主机任意路径 | 用户指定 | 开发时源码热重载 |
| tmpfs mount | 内存 | 宿主机 | 敏感信息,不需要持久化 |
# Volumedocker volume create mydatadocker run -v mydata:/data nginx
# Bind Mount(开发用)docker run -v $(pwd):/app nginx
# tmpfsdocker run --tmpfs /tmp nginxdocker-compose 中的 volume 配置
services: db: image: postgres:16-alpine volumes: # 命名 volume(推荐) - pgdata:/var/lib/postgresql/data # bind mount(开发用) - ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes: pgdata: # 声明命名 volume不要在生产中使用 Bind Mount。Bind Mount 依赖宿主机的目录结构,与 Docker 的隔离理念相悖。生产环境始终使用命名 Volume 或临时存储。
网络配置
Docker 网络模式
| 网络模式 | 说明 | 适用场景 |
|---|---|---|
| bridge(默认) | 容器通过虚拟网桥互通,与宿主机隔离 | 单机多容器通信 |
| host | 容器直接使用宿主机网络栈(无隔离) | 性能敏感场景(如 Nginx) |
| none | 容器无网络 | 完全隔离的离线任务 |
| overlay | 跨宿主机容器通信(Swarm 模式) | 集群部署 |
网络模式性能与隔离性对比
选择建议:
- 🏠 单机开发/测试 →
bridge(默认,最常用) - ⚡ 性能敏感应用(Nginx 反向代理)→
host - 🌐 跨服务器集群 →
overlay(Docker Swarm/Kubernetes)
# 创建自定义 bridge 网络docker network create mynetwork
# 在指定网络中运行容器docker run --network mynetwork --name web nginx
# 同一网络中的容器可以通过服务名互相访问docker run --network mynetwork --name app alpine# app 容器里可以直接 ping webCompose 中的网络
默认情况下,docker compose up 会自动创建一个网络,所有服务加入其中,通过服务名互相访问。
services: web: build: . networks: - frontend - backend api: image: my-api networks: - backend db: image: postgres networks: - backend # 不加入 frontend,更安全
networks: frontend: backend:常用最佳实践
Dockerfile 最佳实践
| 实践 | 说明 |
|---|---|
| 选择小的基础镜像 | node:20-alpine (120MB) vs node:20 (1GB) |
| 利用构建缓存 | 先复制 package.json,再复制源码 |
| 减少层数 | 合并 RUN 命令:RUN apt-get update && apt-get install -y pkg |
| 多阶段构建 | 编译产物小几十 MB,比带整个 SDK 小太多 |
| 使用非 root 用户 | USER node 或创建专用用户 |
添加 .dockerignore | 排除 node_modules、.git 等 |
# ❌ 不推荐RUN apt-get updateRUN apt-get install -y curlRUN apt-get clean
# ✅ 推荐:合并在一个 RUN 中RUN apt-get update && \ apt-get install -y curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/*.dockerignore
node_modules.git.gitignore*.mddocker-compose*.yml.env.env.*安全性
| 建议 | 具体操作 |
|---|---|
| 不运行 root | RUN adduser -D appuser + USER appuser |
| 不存储密钥在镜像中 | 用环境变量或 Secret 注入 |
| 扫描镜像漏洞 | docker scan 或 Trivy |
| 使用受信任的基础镜像 | 优先选官方镜像,带签名验证的 |
| 保持镜像更新 | 定期 docker pull 更新基础镜像 |
实际项目结构示例
- .dockerignore
- Dockerfile
- docker-compose.yml
- docker-compose.dev.yml
- docker-deploy.yml
- index.js
- package.json
- package-lock.json
- README.md
CI/CD 自动化部署
完整部署流程
GitHub Action 配置示例
# .github/workflows/docker-deploy.ymlname: Docker Build and Deploy
on: push: branches: [ main ]
jobs: build-and-deploy: runs-on: ubuntu-latest
steps: - name: Checkout code uses: actions/checkout@v3
- name: Set up Docker Buildx uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push uses: docker/build-push-action@v4 with: context: . push: true tags: user/myapp:latest cache-from: type=registry,ref=user/myapp:buildcache cache-to: type=registry,ref=user/myapp:buildcache,mode=max
- name: Deploy to VPS uses: appleboy/ssh-action@master with: host: ${{ secrets.VPS_HOST }} username: ${{ secrets.VPS_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /srv/myapp docker-compose pull docker-compose up -d --remove-orphans docker image prune -f详细配置可参考:GitHub Action 自动部署
部署成功率统计
常见问题
# 查看磁盘占用docker system df
# 一键清理所有未使用资源(⚠️ 谨慎使用)docker system prune -a --volumes
# 按需清理docker container prune # 清理停止的容器docker image prune # 清理悬空镜像docker volume prune # 清理未使用的 volumedocker builder prune # 清理构建缓存| 资源类型 | 清理命令 | 风险等级 |
|---|---|---|
| 停止的容器 | container prune | 🟢 低 |
| 悬空镜像 | image prune | 🟢 低 |
| 未使用的 Volume | volume prune | 🔴 高(数据丢失) |
| 构建缓存 | builder prune | 🟡 中(重建变慢) |
很多基础镜像(如 Alpine)默认使用 UTC 时区,导致日志时间错误。
解决方案 1:Dockerfile 中设置
ENV TZ=Asia/ShanghaiRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ echo $TZ > /etc/timezone解决方案 2:docker run 时挂载
docker run -v /etc/localtime:/etc/localtime:ro nginx验证:
docker exec <container> date# 应该显示 CST(中国标准时间)错误信息:
Error response from daemon: driver failed programming external connectivityon endpoint web: Bind for 0.0.0.0:8080 failed: port is already allocated解决方法:
# 1. 查看端口占用lsof -i :8080# 或netstat -tuln | grep 8080
# 2. 换一个映射端口docker run -p 8081:80 nginx
# 3. 或停止占用端口的进程kill -9 <PID>场景:容器内的应用需要连接宿主机的数据库或 API。
解决方案:
# macOS / Windowsdocker run -e DB_HOST=host.docker.internal my-app
# Linux(使用 docker0 网桥 IP)ip addr show docker0 | grep inet# 假设输出 172.17.0.1docker run -e DB_HOST=172.17.0.1 my-app或者使用 host 网络模式(仅 Linux):
docker run --network host my-app防止单个容器占用过多资源导致宿主机卡死。Docker 使用 Linux cgroups3Control Groups(控制组)是 Linux 内核提供的一种机制,用于限制、记录和隔离进程组的资源使用(CPU、内存、磁盘 I/O 等)。 技术实现资源限制。
# 限制内存和 CPUdocker run \ --memory="512m" \ --memory-swap="1g" \ --cpus="1.5" \ nginx
# 实时监控docker stats在 docker-compose 中:
services: web: image: nginx deploy: resources: limits: cpus: '1.5' memory: 512M reservations: cpus: '0.5' memory: 256M资源占用趋势图:
学习路线图
- 理解镜像、容器、仓库三个核心概念
- 掌握 Dockerfile 的常用指令
- 能写简单的 Dockerfile 并构建镜像
- 用 docker-compose 编排多容器应用
- 理解数据卷和数据持久化方案
- 配置 docker 网络实现容器通信
- 能进行多阶段构建优化镜像体积
- 了解部署到服务器的基础流程(→ Hexo 部署到搬瓦工 VPS)
总结
| 知识点 | 要点 | 核心命令/指令 |
|---|---|---|
| 镜像构建 | 分层结构、缓存利用、多阶段构建 | FROM、COPY、RUN、docker build |
| 容器管理 | 生命周期、端口映射、exec 进入 | docker run、exec、stop、rm |
| Compose | 多容器编排,YAML 定义服务 | docker compose up、down |
| 数据持久化 | Volume 是推荐方式,bind mount 用于开发 | -v、volumes: |
| 网络 | bridge 单机通信,overlay 跨主机 | docker network、networks: |
| 安全 | 非 root 用户、镜像扫描、密钥注入 | USER、docker scan |
Docker 的入门门槛不高,不需要死记硬背所有命令,理解镜像分层和容器生命周期这两个核心概念后,其他都可以边用边查。
评论
GitHub 登录后可评论。
评论区会在滚动到这里时自动加载。