Docker 容器化从 Dockerfile 到 Compose 编排

FreeGuideOnline 最新 2026-06-12

Docker 容器化应用部署入门到实践:从 Dockerfile 到 Compose 编排

本教程面向零基础开发者,系统讲解如何通过 Docker 将应用容器化,逐步掌握从编写 Dockerfile、构建镜像,到利用 Docker Compose 编排多服务应用的全流程。你将通过两个完整实例(单服务 Node.js 应用、多服务 Web+Redis 应用)边学边练,最终具备独立部署中小型应用的能力。

1. 基本功:理解容器与搭建环境

1.1 Docker 解决了什么问题

传统部署方式下,不同机器间的环境差异(操作系统、依赖库版本、配置文件)常导致“我这能跑,你那不行”的问题。Docker 通过容器技术将应用与其运行环境整体打包,形成轻量、可移植的镜像,运行时就成为相互隔离的容器。这与虚拟机相比,粒度更精细、资源消耗更低。

核心三要素:

  • 镜像(Image):应用的模板,包含代码、运行时、系统库等。
  • 容器(Container):镜像的运行实例,可启动、停止、删除。
  • 仓库(Registry):存放镜像的地方(如 Docker Hub,私有仓库 Harbor 等)。

1.2 安装 Docker 与 Docker Compose

在 Linux/macOS/Windows 上均可安装 Docker Desktop(自带 Compose)或单独安装 Docker Engine。安装后执行以下命令验证:

docker --version
docker compose version

如果你的系统尚未安装,可参考官方文档:

  • Linux:使用脚本自动安装 curl -fsSL https://get.docker.com | sh
  • macOS/Windows:下载 Docker Desktop 安装包

1.3 配置镜像加速(推荐)

国内访问 Docker Hub 较慢,建议配置镜像加速器(如阿里云、腾讯云等)。以 Linux 为例,编辑 /etc/docker/daemon.json

{
  "registry-mirrors": ["https://你的镜像加速地址"]
}

重启 Docker 服务生效。

2. 编写应用的说明书:Dockerfile

Dockerfile 是一个文本文件,包含构建镜像所需的全部指令。理解它是迈入容器化部署的第一步。

2.1 常用指令速查

指令 作用 示例
FROM 指定基础镜像 FROM node:18-alpine
WORKDIR 设置工作目录 WORKDIR /app
COPY 将文件从宿主机复制到镜像 COPY package*.json ./
RUN 在镜像构建时执行命令 RUN npm install
CMD 容器启动时默认执行的命令 CMD ["node", "server.js"]
EXPOSE 声明容器监听的端口 EXPOSE 3000
ENV 设置环境变量 ENV NODE_ENV=production
ENTRYPOINT 结合 CMD 使用,定义容器入口 ENTRYPOINT ["docker-entrypoint.sh"]

2.2 实战:编写 Node.js 应用的 Dockerfile

假设有一个简单的 Express 应用,目录结构如下:

myapp/
├── package.json
├── server.js
└── public/

package.json 示例片段:

{
  "name": "myapp",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}

server.js 内容:

const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => res.send('Hello Docker!'));
app.listen(port, () => console.log(`App running on port ${port}`));

在项目根目录创建 Dockerfile(无扩展名):

# 第一步:选择极精简的Node.js基础镜像
FROM node:18-alpine

# 第二步:设置工作目录
WORKDIR /app

# 第三步:优先复制依赖描述文件,利用缓存层
COPY package*.json ./

# 第四步:安装生产依赖
RUN npm ci --only=production

# 第五步:复制应用源码
COPY . .

# 第六步:暴露端口(仅声明,实际映射需在运行时指定)
EXPOSE 3000

# 第七步:定义健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1

# 第八步:容器启动命令
CMD ["node", "server.js"]

2.3 编写 Dockerfile 的最佳实践

  • 选择合适的基础镜像:优先使用 alpine 版本,体积小、攻击面少。
  • 利用构建缓存:先 COPY 不常变的文件(如 package.json)并执行安装,再 COPY 频繁改动的源码。
  • 减少层数:将多条 RUN 命令合并为一条,并用 && 连接,同时清理缓存(rm -rf /var/lib/apt/lists/*)。
  • 不要以 root 运行:通过 RUN addgroupUSER 指令切换至非特权用户。
  • 使用 .dockerignore:忽略 node_modules.git 等无关文件,避免复制到镜像。

示例 .dockerignore

node_modules
npm-debug.log
.git
.env

3. 构建镜像与运行容器

3.1 构建镜像

myapp/ 目录下执行:

docker build -t myapp:1.0 .
  • -t 指定镜像名称和标签(tag)
  • . 表示构建上下文为当前目录

构建完成后查看镜像列表:

docker images

3.2 运行容器并映射端口

docker run -d --name myapp-container -p 8080:3000 myapp:1.0
  • -d 后台运行
  • --name 容器命名,便于管理
  • -p 宿主机端口:容器端口,访问 http://localhost:8080 即可看到 “Hello Docker!”

常用管理命令:

docker ps           # 查看运行中的容器
docker stop myapp-container
docker start myapp-container
docker rm myapp-container   # 删除已停止的容器
docker logs myapp-container  # 查看日志

3.3 数据持久化与卷挂载

容器删除后内部数据会丢失。可通过两种方式实现持久化:

1. 绑定挂载 – 将宿主机目录映射到容器:

docker run -d -p 8080:3000 -v $(pwd)/data:/app/data myapp:1.0

2. 命名卷 – 由 Docker 管理,易迁移和备份:

docker volume create app-data
docker run -d -p 8080:3000 -v app-data:/app/data myapp:1.0

4. 多容器协同:走进 Docker Compose

当应用由多个服务(如 Web + 数据库 + 缓存)组成时,手动管理每个容器的启动顺序、网络连接、卷挂载会非常繁琐。Docker Compose 提供声明式的 YAML 文件来定义和运行一套服务。

4.1 Compose 文件核心结构

一个典型的 docker-compose.yml 包含以下顶层元素:

元素 说明
services 定义各个服务(容器),是 Compose 的主干
networks 自定义网络,服务间默认通过服务名通信
volumes 定义可共享的命名卷

4.2 实战:定义 Web + Redis 应用

我们扩充之前的 Node.js 应用,使其访问 Redis 记录页面访问次数。

更新后的 server.js:

const express = require('express');
const redis = require('redis');
const app = express();

const client = redis.createClient({ url: 'redis://redis:6379' });
client.on('error', (err) => console.log('Redis Client Error', err));
client.connect().then(() => console.log('Connected to Redis'));

app.get('/', async (req, res) => {
  const visits = await client.incr('visits');
  res.send(`Hello Docker! This page has been visited ${visits} times.`);
});

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`App running on port ${port}`));

更新 package.json 依赖,加入 redis

docker-compose.yml 文件:

version: "3.9"

services:
  web:
    build: .
    ports:
      - "8080:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    depends_on:
      - redis
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    restart: unless-stopped

volumes:
  redis-data:

这个 Compose 文件完成了以下几件事:

  • 构建 web 服务(使用当前目录的 Dockerfile)
  • 拉取 redis 官方镜像并挂载命名卷持久化数据
  • depends_on 保证 Redis 先启动(但不确保 service 完全就绪,后续可加入 healthcheck 和 depends_on condition)
  • 两个服务默认处于同一自定义网络,web 可直接通过 redis 主机名访问 Redis

4.3 启动、停止与扩展

docker-compose.yml 所在目录执行:

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

# 查看服务状态
docker compose ps

# 查看各服务日志
docker compose logs -f

# 暂停所有服务
docker compose stop

# 完全删除容器、网络(卷默认保留)
docker compose down

# 删除同时清除卷
docker compose down -v

为了验证 Redis 工作,浏览器多次访问 http://localhost:8080,每次计数应增加。

扩展服务数量 对于无状态服务,可轻松伸缩:

docker compose up -d --scale web=3

此时 Docker 会以 web 服务为模板创建 3 个实例,并通过内置的 DNS 负载均衡分发请求。需要注意:如果端口映射固定为 8080:3000,会因宿主机端口冲突而失败。通常在生产中会使用反向代理(如 Nginx)处理,此处不做深入。

5. 编排实践:微服务型应用示例

让我们构建一个更接近真实场景的示例:采用 Python Flask (API服务) + Redis (缓存) + Nginx (反向代理) 的结构,展示 Compose 中网络隔离、环境变量控制、健康检查定义的完整用法。

5.1 项目目录结构

flask-redis-nginx/
├── api/
│   ├── Dockerfile
│   ├── requirements.txt
│   └── app.py
├── nginx/
│   ├── Dockerfile
│   └── nginx.conf
└── docker-compose.yml

5.2 API 服务 (Flask)

api/app.py:

from flask import Flask, jsonify
import redis
import os

app = Flask(__name__)
cache = redis.Redis(host=os.environ.get('REDIS_HOST', 'redis'),
                    port=6379, decode_responses=True)

@app.route('/')
def index():
    visits = cache.incr('hits')
    return jsonify({"message": "Hello from Flask", "visits": visits})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

api/requirements.txt:

flask
redis

api/Dockerfile:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
HEALTHCHECK --interval=10s --timeout=3s \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/')" || exit 1
CMD ["python", "app.py"]

5.3 Nginx 反向代理

nginx/nginx.conf:

events {}

http {
  upstream flask_app {
    server api:5000;
  }

  server {
    listen 80;

    location / {
      proxy_pass http://flask_app;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
    }
  }
}

nginx/Dockerfile:

FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
HEALTHCHECK --interval=10s --timeout=3s \
  CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1

5.4 编写总编排文件

docker-compose.yml:

version: "3.9"

services:
  api:
    build: ./api
    environment:
      - REDIS_HOST=redis
    networks:
      - backend
    depends_on:
      redis:
        condition: service_healthy
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    networks:
      - backend
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped

  nginx:
    build: ./nginx
    ports:
      - "80:80"
    networks:
      - frontend
      - backend
    depends_on:
      - api
    restart: unless-stopped

networks:
  frontend:
  backend:

volumes:
  redis-data:

本编排实现了:

  • 网络隔离:API 和 Redis 处于 backend 网络,Nginx 同时连接 backendfrontend,对外只暴露 Nginx。
  • 启动顺序控制:通过 depends_on 配合 healthcheck,确保 Redis 健康之后再启动 API 服务。
  • 数据持久化:Redis 使用 AOF 模式,卷 redis-data 保证断电不丢数据。

启动整个栈:

docker compose up -d

访问 http://localhost,即可看到 JSON 响应,每次刷新 visits 递增。

5.5 调试与常用命令

  • 进入容器内部docker compose exec api sh
  • 临时运行一次性命令docker compose run --rm api python -c "import redis; ..."
  • 重新构建单个服务docker compose up -d --no-deps --build api
  • 查看资源使用docker stats

6. 从实践走向生产:下一步建议

  • 镜像优化:使用多阶段构建减少最终镜像体积,移除构建工具。
  • 安全扫描:集成 docker scan 或 Trivy 检查镜像漏洞。
  • 持续集成:在 CI 流程中自动构建、测试并推送镜像至私有仓库。
  • 编排升级:当服务规模进一步扩大,可迁移至 Kubernetes;Docker Compose 可平滑过渡,两者概念相通。
  • 配置管理:敏感信息使用 Docker Secrets(Swarm 模式)或外部加密服务,不在镜像和环境变量中硬编码。

7. 常见问题排查

  • 容器启动后立即退出:检查 CMD 指定的进程是否前台运行,查看日志 docker logs <容器名>
  • 依赖服务连不上:确认 Compose 网络配置,服务间应使用服务名通信;若使用 depends_on,应用程序需具备重试逻辑应对服务的短暂不可用。
  • 端口冲突:修改宿主机映射端口或停止占用进程。
  • 构建缓存未更新:使用 docker compose build --no-cache 强制重新构建。

通过本教程,你已掌握从单一 Dockerfile 编写到多服务 Compose 编排的完整技能链。即刻动手,将自己的项目容器化吧!