上一篇 下一篇 分享链接 返回 返回顶部

Docker 自动化部署实战:从构建镜像到一键上线

发布人:慈云数据-客服中心 发布时间:10小时前 阅读量:0

Docker 工作流自动化教程|一键部署

在现代软件开发中,应用的部署方式正在从“手工配置服务器”逐渐走向“自动化、标准化、可复制”。过去我们部署一个项目,往往需要登录服务器、安装运行环境、配置依赖、修改配置文件、启动进程、排查端口冲突等。不同服务器之间环境差异明显,开发环境能跑,到了测试或生产环境却频繁出错。

Docker 的出现很好地解决了这类问题。它通过容器化技术,将应用、运行环境、依赖库、配置文件等打包到一个镜像中,使应用可以在不同机器上以几乎一致的方式运行。而当 Docker 与自动化脚本、Docker Compose、CI/CD 工具结合后,就可以构建一套完整的工作流,实现从代码提交到服务部署的一键化操作。

本文将围绕 Docker 工作流自动化 展开,带你从基础概念、项目结构、镜像构建、容器编排、自动化脚本,到一键部署实践,系统掌握如何使用 Docker 提升部署效率。


一、为什么需要 Docker 工作流自动化?

在没有自动化部署之前,一个常见的部署流程可能是这样的:

  1. 登录服务器;
  2. 拉取最新代码;
  3. 安装或更新项目依赖;
  4. 修改环境变量;
  5. 执行数据库迁移;
  6. 构建前端资源;
  7. 停止旧服务;
  8. 启动新服务;
  9. 查看日志确认是否成功;
  10. 出现问题后再手动回滚。

这个过程不仅繁琐,而且容易出错。尤其当项目参与人员较多、服务数量增加、部署频率提高时,手动操作会成为明显的效率瓶颈。

Docker 工作流自动化的目标,就是将上述步骤沉淀为标准流程,通过脚本和配置文件实现:

  • 一键构建镜像
  • 一键启动服务
  • 一键更新版本
  • 一键查看日志
  • 一键回滚部署
  • 多环境一致运行
  • 降低人为操作失误

从开发者角度看,自动化部署不仅节省时间,也能让团队成员更容易理解项目运行方式。只要项目仓库中包含 Dockerfiledocker-compose.yml 和部署脚本,新成员就可以快速启动完整环境。


二、Docker 自动化工作流的核心组成

一个完整的 Docker 自动化工作流通常由以下几个部分组成:

组成部分 作用
Dockerfile 定义镜像如何构建
docker-compose.yml 定义多容器服务如何启动
.env 管理环境变量
Makefile 或 Shell 脚本 封装常用命令
CI/CD 配置 自动构建、测试、发布
镜像仓库 存储和分发镜像
日志与监控 观察服务状态

其中,Dockerfiledocker-compose.yml 是最基础也是最重要的两个文件。前者解决“如何打包应用”的问题,后者解决“如何运行应用及其依赖服务”的问题。

例如,一个后端项目可能依赖 MySQL、Redis、Nginx。通过 Docker Compose,我们可以在一个配置文件中描述这些服务,然后使用一条命令启动整个系统:

docker compose up -d

相比手动分别启动数据库、缓存、Web 服务,这种方式更加统一、可靠,也更适合自动化部署。


三、准备一个示例项目结构

为了方便说明,我们假设有一个 Node.js 后端项目,项目目录如下:

my-docker-app/
├── src/
│   └── app.js
├── package.json
├── package-lock.json
├── Dockerfile
├── docker-compose.yml
├── .env
├── deploy.sh
└── Makefile

其中:

  • src/app.js 是应用入口;
  • package.json 记录项目依赖;
  • Dockerfile 用于构建应用镜像;
  • docker-compose.yml 用于定义服务;
  • .env 用于存放环境变量;
  • deploy.sh 用于执行一键部署;
  • Makefile 用于封装常用命令。

示例应用代码如下:

// src/app.js
const express = require('express');

const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello Docker Workflow Automation!');
});

app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    time: new Date().toISOString()
  });
});

app.listen(port, () => {
  console.log(`App is running on port ${port}`);
});

package.json 示例:

{
  "name": "my-docker-app",
  "version": "1.0.0",
  "description": "Docker workflow automation demo",
  "main": "src/app.js",
  "scripts": {
    "start": "node src/app.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}

四、编写 Dockerfile:让应用可复制构建

Dockerfile 是 Docker 镜像构建的说明书。它定义了应用运行所需的基础镜像、依赖安装、文件复制、启动命令等。

一个简单但相对规范的 Node.js Dockerfile 如下:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./

RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

逐行解释:

FROM node:20-alpine

表示使用 Node.js 20 的 Alpine 版本作为基础镜像。Alpine 镜像体积较小,适合生产环境。

WORKDIR /app

设置容器内部的工作目录为 /app

COPY package*.json ./

先复制依赖描述文件,而不是直接复制所有代码。这样做可以充分利用 Docker 构建缓存。只要依赖文件没变,后续构建时就不需要重新安装依赖。

RUN npm ci --only=production

安装生产依赖。npm cinpm install 更适合自动化环境,因为它会严格按照 package-lock.json 安装依赖。

COPY . .

复制项目代码到镜像中。

EXPOSE 3000

声明容器内部服务端口。

CMD ["npm", "start"]

容器启动时执行应用启动命令。

为了减少镜像构建上下文,建议添加 .dockerignore 文件:

node_modules
npm-debug.log
.git
.gitignore
Dockerfile
docker-compose.yml
.env
logs
dist

.dockerignore 的作用类似 .gitignore,可以避免无关文件被复制到镜像构建上下文中,从而减少镜像体积并提高构建速度。


五、使用 Docker Compose 编排服务

单个应用容器比较简单,但实际项目中通常需要多个服务协同运行。例如 Web 应用、数据库、Redis、Nginx 等。此时可以使用 Docker Compose。

下面是一个包含应用和 Redis 的 docker-compose.yml 示例:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: my-docker-app:latest
    container_name: my-docker-app
    ports:
      - "${APP_PORT}:3000"
    environment:
      NODE_ENV: production
      PORT: 3000
      REDIS_HOST: redis
      REDIS_PORT: 6379
    depends_on:
      - redis
    restart: unless-stopped
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    container_name: my-docker-redis
    volumes:
      - redis-data:/data
    restart: unless-stopped
    networks:
      - app-network

volumes:
  redis-data:

networks:
  app-network:
    driver: bridge

对应 .env 文件:

APP_PORT=3000

启动服务:

docker compose up -d

查看运行状态:

docker compose ps

查看日志:

docker compose logs -f app

停止服务:

docker compose down

这里有几个关键点值得注意。

首先,depends_on 可以控制服务启动顺序,但它并不代表被依赖服务已经完全可用。例如 Redis 容器启动了,并不一定表示 Redis 已经准备好处理请求。因此在生产项目中,应用层通常还需要添加重试机制,或者使用健康检查。

其次,restart: unless-stopped 表示容器异常退出后会自动重启,除非用户主动停止。这对生产服务非常有用。

再次,volumes 用于持久化数据。像 Redis、MySQL、PostgreSQL 等服务,如果不配置数据卷,容器删除后数据也可能丢失。


六、封装常用命令:使用 Makefile 提升效率

在团队协作中,如果每个人都需要记住一堆 Docker 命令,会增加沟通成本。因此我们可以使用 Makefile 封装常用操作。

示例 Makefile

APP_NAME=my-docker-app

build:
    docker compose build

up:
    docker compose up -d

down:
    docker compose down

restart:
    docker compose down
    docker compose up -d

logs:
    docker compose logs -f app

ps:
    docker compose ps

clean:
    docker compose down -v
    docker system prune -f

deploy:
    sh deploy.sh

之后常用命令就可以简化为:

make build
make up
make logs
make deploy

这种方式的好处是命令语义清晰,新成员只需要查看 Makefile 就能知道项目支持哪些操作。同时也可以避免不同成员手动输入命令时出现参数不一致的问题。


七、编写一键部署脚本 deploy.sh

一键部署脚本是 Docker 工作流自动化的核心。它可以将拉取代码、构建镜像、启动服务、清理资源、检查状态等操作整合在一起。

下面是一个基础版 deploy.sh

#!/bin/bash

set -e

echo "=============================="
echo "开始部署 my-docker-app"
echo "=============================="

echo "1. 拉取最新代码"
git pull

echo "2. 构建 Docker 镜像"
docker compose build

echo "3. 停止旧容器并启动新容器"
docker compose up -d

echo "4. 查看容器状态"
docker compose ps

echo "5. 清理无用镜像"
docker image prune -f

echo "=============================="
echo "部署完成"
echo "=============================="

给脚本添加执行权限:

chmod +x deploy.sh

执行部署:

./deploy.sh

或者:

make deploy

set -e 的含义是:当脚本中某一步命令执行失败时,立即终止脚本,避免继续执行后续操作导致状态混乱。

不过,基础版脚本还不够完善。在生产环境中,我们通常希望部署脚本具备以下能力:

  • 检查 Docker 是否安装;
  • 检查 .env 文件是否存在;
  • 自动备份当前版本信息;
  • 支持健康检查;
  • 部署失败自动提示;
  • 可选择是否清理旧镜像;
  • 输出更清晰的日志。

下面给出一个增强版部署脚本。

#!/bin/bash

set -e

APP_NAME="my-docker-app"
HEALTH_URL="http://localhost:3000/health"

log() {
  echo ""
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

check_command() {
  if ! command -v "$1" >/dev/null 2>&1; then
    echo "错误:未检测到命令 $1,请先安装。"
    exit 1
  fi
}

log "开始部署 ${APP_NAME}"

log "检查基础环境"
check_command docker
check_command git

if [ ! -f ".env" ]; then
  echo "错误:未找到 .env 文件,请先创建环境变量配置。"
  exit 1
fi

log "拉取最新代码"
git pull

log "构建镜像"
docker compose build

log "启动服务"
docker compose up -d

log "等待服务启动"
sleep 5

log "执行健康检查"
if curl -f "${HEALTH_URL}" >/dev/null 2>&1; then
  echo "健康检查通过:${HEALTH_URL}"
else
  echo "健康检查失败,请查看日志:"
  docker compose logs --tail=100 app
  exit 1
fi

log "当前容器状态"
docker compose ps

log "清理无用镜像"
docker image prune -f

log "部署成功"

这个脚本在部署完成后会请求 /health 接口,如果应用没有正常响应,则输出最近日志并退出。这种健康检查机制非常实用,可以帮助我们更快发现部署异常。


八、镜像版本管理:避免 latest 带来的不确定性

很多初学者喜欢使用 latest 标签,例如:

image: my-docker-app:latest

在本地测试中这没有太大问题,但在生产环境中,latest 容易导致版本不明确。我们无法直观看出当前运行的是哪一次构建,也不方便回滚。

更推荐的做法是使用 Git Commit ID、版本号或构建时间作为镜像标签。例如:

IMAGE_TAG=$(git rev-parse --short HEAD)
docker build -t my-docker-app:${IMAGE_TAG} .

在 CI/CD 中,可以生成类似这样的镜像:

my-registry.com/my-docker-app:1.0.0
my-registry.com/my-docker-app:20250201-153000
my-registry.com/my-docker-app:a1b2c3d

这样做有几个好处:

  1. 版本可追踪:可以明确知道镜像来自哪次代码提交;
  2. 便于回滚:如果新版本有问题,可以快速切换到旧版本;
  3. 方便审计:部署记录更加清晰;
  4. 减少歧义:避免不同机器上的 latest 指向不同内容。

如果使用 Docker Compose,可以通过 .env 管理镜像标签:

IMAGE_TAG=a1b2c3d
APP_PORT=3000

docker-compose.yml 中使用:

services:
  app:
    image: my-docker-app:${IMAGE_TAG}
    ports:
      - "${APP_PORT}:3000"

部署时更新 .env 中的 IMAGE_TAG,即可切换版本。


九、接入 CI/CD:从提交代码到自动部署

当本地 Docker 工作流已经稳定后,可以进一步接入 CI/CD,实现代码提交后自动构建和部署。常见工具包括:

  • GitHub Actions
  • GitLab CI/CD
  • Jenkins
  • Gitea Actions
  • Drone
  • Argo CD

以 GitHub Actions 为例,一个典型流程是:

  1. 开发者提交代码到主分支;
  2. GitHub Actions 自动运行测试;
  3. 测试通过后构建 Docker 镜像;
  4. 将镜像推送到镜像仓库;
  5. 服务器拉取新镜像并重启服务。

下面是一个简化版 GitHub Actions 配置:

name: Docker Deploy

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set image tag
        run: echo "IMAGE_TAG=${GITHUB_SHA::7}" >> $GITHUB_ENV

      - name: Login Docker Registry
        run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin

      - name: Build image
        run: docker build -t yourname/my-docker-app:${IMAGE_TAG} .

      - name: Push image
        run: docker push yourname/my-docker-app:${IMAGE_TAG}

      - name: Deploy to server
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cd /opt/my-docker-app
            git pull
            echo "IMAGE_TAG=${{ env.IMAGE_TAG }}" > .env
            echo "APP_PORT=3000" >> .env
            docker compose pull
            docker compose up -d
            docker image prune -f

这里使用了 GitHub Secrets 保存敏感信息,例如服务器地址、SSH 密钥、Docker Hub 密码等。不要把密码直接写在仓库配置文件中。

CI/CD 能够进一步减少人工操作,让部署过程更加标准化。但需要注意,自动部署到生产环境前,最好增加测试、审批或灰度发布环节,避免有问题的代码直接影响用户。


十、生产环境部署建议

Docker 让部署变得简单,但生产环境仍然需要谨慎设计。以下是一些实用建议。

1. 不要把敏感信息写进镜像

例如数据库密码、API Key、Token 等,不应该写在 Dockerfile 或代码中。推荐通过环境变量、配置中心、Secret 管理工具传入。

错误示例:

ENV DB_PASSWORD=123456

更好的方式是:

environment:
  DB_PASSWORD: ${DB_PASSWORD}

然后在服务器 .env 中配置:

DB_PASSWORD=your_strong_password

2. 容器内尽量只运行一个主进程

一个容器最好只负责一个服务。例如应用容器只运行 Web 应用,数据库放在单独容器中。这样便于扩容、重启、监控和排错。

3. 配置健康检查

Docker Compose 支持 healthcheck

services:
  app:
    image: my-docker-app:latest
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3

健康检查可以帮助 Docker 或编排系统判断容器状态。

4. 持久化重要数据

数据库、上传文件、日志等需要持久化的数据,应使用 Docker Volume 或挂载宿主机目录。否则容器重建时可能造成数据丢失。

volumes:
  mysql-data:

5. 限制容器资源

为了防止单个容器占用过多资源,可以设置 CPU 和内存限制。不同 Docker Compose 版本写法略有差异,在生产环境中应根据实际情况配置。

6. 定期清理无用资源

长期构建镜像会产生大量无用镜像和缓存。可以定期执行:

docker system df
docker image prune
docker builder prune

但在生产环境清理资源时要谨慎,避免误删仍需使用的数据卷。


十一、常见问题与排查方法

1. 容器启动后立即退出

查看日志:

docker compose logs app

常见原因包括:

  • 启动命令错误;
  • 依赖缺失;
  • 环境变量未配置;
  • 端口冲突;
  • 程序运行时报错。

2. 端口无法访问

检查容器状态:

docker compose ps

检查端口映射是否正确:

ports:
  - "3000:3000"

如果服务器使用了防火墙,还需要确认安全组或防火墙是否开放端口。

3. 修改代码后没有生效

可能原因:

  • 镜像没有重新构建;
  • 容器没有重新创建;
  • 浏览器或代理缓存;
  • 部署到了错误服务器或错误目录。

可以执行:

docker compose build --no-cache
docker compose up -d

4. docker compose 命令不可用

新版 Docker 使用:

docker compose

旧版可能是:

docker-compose

建议升级 Docker 到较新版本,统一使用 docker compose


十二、推荐的一键部署完整流程

综合前面的内容,一个适合中小型项目的 Docker 一键部署流程可以设计为:

  1. 本地编写代码;
  2. 提交代码到 Git 仓库;
  3. 服务器执行部署脚本;
  4. 脚本拉取最新代码;
  5. 构建或拉取 Docker 镜像;
  6. 使用 Docker Compose 重启服务;
  7. 执行健康检查;
  8. 输出部署结果;
  9. 清理无用镜像;
  10. 如失败则查看日志并回滚。

对于团队来说,可以约定以下命令:

make deploy
make logs
make ps
make restart

这样部署就不再依赖某个人的经验,而是由脚本和配置固化下来。无论是新人接手项目,还是服务器迁移,都能显著降低成本。


十三、总结

Docker 工作流自动化的核心价值,在于把复杂、易错、依赖人工经验的部署过程,变成可重复、可追踪、可回滚的标准流程。

通过本文的实践,我们完成了以下内容:

  • 使用 Dockerfile 构建应用镜像;
  • 使用 .dockerignore 优化构建上下文;
  • 使用 Docker Compose 编排多服务;
  • 使用 .env 管理环境变量;
  • 使用 Makefile 封装常用命令;
  • 使用 deploy.sh 实现一键部署;
  • 使用健康检查提升部署可靠性;
  • 使用镜像标签管理版本;
  • 初步接入 CI/CD 自动化部署。

对于个人项目而言,这套流程可以让部署更加轻松;对于团队项目而言,它能够统一环境、减少沟通成本、提升交付效率。随着项目规模扩大,还可以进一步引入 Kubernetes、服务网格、集中式日志、监控告警、灰度发布等能力。

但无论技术栈如何演进,自动化部署的本质始终不变:让部署过程标准化,让系统状态可控,让每一次上线都更稳定、更高效。

目录结构
全文