使用 Docker 和 Docker Compose 实现高效部署

Docker项目部署详解教程

引言

Docker作为现代软件开发和部署的重要工具,极大地改变了应用交付的方式。本教程将详细讲解如何使用Docker部署项目,从基础概念到实际操作,帮助开发者掌握容器化部署技术。

1. Docker基础概念与价值

1.1 Docker与传统虚拟机的对比

Docker容器技术相比传统虚拟机有显著优势:

特性 容器 虚拟机
启动时间 秒级 分钟级
存储空间 通常为MB级别 通常为GB级别
性能表现 接近原生性能 性能有所损耗
资源利用率 单机可支持上千个容器 一般支持几十个虚拟机
操作系统 共享宿主机内核 需要完整的Guest OS

1.2 Docker核心概念

Docker生态系统中有三个基本概念,理解它们是掌握Docker的关键:

  • 镜像(Image):只读的模板,包含运行应用所需的程序、依赖、配置等。镜像构建完成后是不可变的,仅用于启动容器。

  • 容器(Container):镜像的运行实例,可以被启动、停止、删除。每个容器彼此独立且安全隔离,可以理解为一个轻量级的操作系统,专门用来运行特定应用。

  • 仓库(Repository):存储和分发Docker镜像的服务,如Docker Hub(类似于GitHub),用户可以在上面分享和获取镜像。

Docker的工作流程是:通过Dockerfile定义镜像的构建过程→构建镜像或从仓库拉取镜像→使用镜像创建并运行容器

1.3 为什么要使用Docker

Docker为现代开发与部署带来了多方面的优势:

  • 环境一致性:彻底解决"在我的电脑上能运行"的问题,确保开发、测试和生产环境完全一致
  • 版本隔离:在同一台服务器上运行不同版本的应用(如不同版本的Node.js、Python等)
  • 服务迁移:容器化后的应用可以轻松地在不同服务器间迁移,无需担心环境差异
  • 资源效率:比传统虚拟机更轻量,资源占用更小
  • 标准化交付:提供统一的应用交付标准,简化部署流程,减少人为错误
  • 微服务友好:特别适合微服务架构,每个服务独立容器化

虽然前端开发可能日常工作中较少直接接触容器化技术,但了解Docker已成为全栈开发者的基本素养,能显著提升项目的可维护性和部署效率。

2. Docker环境搭建

2.1 Docker安装

Docker分为稳定版(stable)、测试版(test)和每日构建版(nightly)三个更新频道。根据不同操作系统,安装方法略有不同:

Linux安装

# Ubuntu系统
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

# CentOS系统
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker

Windows/Mac安装

直接下载并安装Docker Desktop,该软件提供了图形界面,更易于管理容器和镜像。

2.2 验证安装

安装完成后,通过以下命令验证安装是否成功:

docker --version
docker info

如果安装正确,可以尝试运行一个简单的Nginx容器测试:

docker run -d -p 80:80 --name webserver nginx

成功后,访问http://localhost应该能看到Nginx的欢迎页面。测试完成后可以停止并删除容器:

docker stop webserver
docker rm webserver

2.3 配置镜像加速

在中国大陆使用Docker时,从官方仓库拉取镜像可能较慢,可以配置国内镜像源加速:

"registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com",
    "https://docker.1ms.run",
    "https://docker.1panel.dev"
]

在Docker Desktop中,可以通过Settings/Preferences → Docker Engine,添加上述配置并应用重启。

3. 前端项目容器化

3.1 准备工作

要将Vue前端项目容器化,需要创建以下三个关键文件:

  1. Dockerfile - 定义如何构建Docker镜像
  2. .dockerignore - 指定构建时要排除的文件
  3. nginx配置文件 - 配置前端应用的web服务器

Dockerfile

# 多阶段构建:第一阶段 - 构建应用
FROM node:16.14.2 AS builder

# 设置工作目录
WORKDIR /app

# 先复制package.json文件
COPY package*.json ./

# 安装依赖
RUN yarn install

# 复制所有源代码
COPY . .

# 构建应用
RUN npm run build

# 多阶段构建:第二阶段 - 配置Nginx服务
FROM nginx:stable-alpine

# 从构建阶段复制构建结果到Nginx目录
COPY --from=builder /app/dist /usr/share/nginx/html

# 复制自定义Nginx配置
COPY nginx/default.conf /etc/nginx/conf.d/default.conf

# 暴露80端口
EXPOSE 80

# 启动Nginx服务
CMD ["nginx", "-g", "daemon off;"]

这里使用了Docker的多阶段构建功能,可以有效减小最终镜像的大小:

  • 第一阶段使用Node.js环境构建应用
  • 第二阶段仅包含Nginx和构建结果,舍弃了Node.js环境和源代码

.dockerignore文件

.DS_Store
node_modules
/dist

# 本地环境文件
.env.local
.env.*.local

# 日志文件
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# 编辑器目录和文件
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
dist.zip
.git

.dockerignore文件类似于.gitignore,用于指定构建Docker镜像时要排除的文件和目录,这可以提高构建速度并减小镜像体积。

Nginx配置文件

server {
    listen 80;
    server_name localhost;
    
    # 静态资源配置
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;  # 支持SPA应用路由
    }

    # 错误页面配置
    error_page  404              /404.html;
    error_page  500 502 503 504  /50x.html;

    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

这个配置文件设置了Nginx如何提供前端应用。特别是try_files $uri $uri/ /index.html这一行配置支持了单页应用(SPA)的路由功能。

3.2 构建前端项目镜像

创建好上述文件后,在项目根目录执行以下命令构建Docker镜像:

docker build -t frontend-app:v1 .

其中:

  • -t frontend-app:v1 给镜像命名为frontend-app,标签为v1
  • . 表示使用当前目录作为构建上下文

构建成功后,可以通过以下命令查看已创建的镜像:

docker images

3.3 运行前端容器

使用以下命令从刚才构建的镜像创建并运行容器:

docker run -d -p 3000:80 --name frontend-container frontend-app:v1

参数说明:

  • -d 表示在后台运行容器
  • -p 3000:80 将主机的3000端口映射到容器的80端口
  • --name frontend-container 给容器命名为frontend-container

现在可以通过访问http://localhost:3000查看部署的前端应用。

4. 后端服务容器化

4.1 创建简易Node.js后端服务

首先创建一个简单的Node.js后端服务作为示例:

const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');

const app = new Koa();
const router = new Router();

app.use(bodyParser());

// 通用响应处理函数
async function handleRequest(ctx) {
    try {
        ctx.body = {
            "code": 200,
            "status": 1,
            "message": "ok",
            "data": {
                "userRole": 1,
                "userId": "000000000000000001",
                "companyId": "1000000000000000001",
                "userName": "test",
            }
        }
    } catch(error) {
        ctx.status = 500;
        ctx.body = {
            error: 'Internal Server Error'
        };
    }
}

// 登录接口
router.post('/Account/SignIn', async (ctx) => {
    await handleRequest(ctx);
});

// 使用路由中间件
app.use(router.routes());
app.use(router.allowedMethods());

// 启动服务器
const port = process.env.PORT || 1000;
app.listen(port, () => {
    console.log(`服务器运行在端口 ${port}`);
});

创建package.json文件:

{
  "name": "backend-service",
  "version": "1.0.0",
  "type": "commonjs",
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  },
  "dependencies": {
    "koa": "^2.15.2",
    "koa-bodyparser": "^4.4.1",
    "koa-router": "^12.0.1"
  },
  "devDependencies": {
    "nodemon": "^3.1.9"
  }
}

4.2 后端服务Dockerfile

为后端服务创建Dockerfile:

# 使用Node.js官方镜像作为基础镜像
FROM node:16.14.2-alpine

# 设置工作目录
WORKDIR /app

# 复制package.json和package-lock.json
COPY package*.json ./

# 安装依赖
RUN npm ci --production

# 复制项目文件
COPY . .

# 设置环境变量
ENV PORT=1000
ENV NODE_ENV=production

# 暴露端口
EXPOSE 1000

# 启动服务
CMD ["node", "app.js"]

使用alpine版本的Node.js镜像可以大幅减小镜像体积。npm ci --production命令只安装生产环境依赖,进一步减小镜像大小。

4.3 构建并运行后端服务镜像

构建后端服务镜像:

docker build -t backend-service:v1 -f Dockerfile.backend .

运行后端服务容器:

docker run -d --name backend-container -p 9000:1000 backend-service:v1

可以使用Postman或curl测试后端服务是否正常工作:

curl -X POST http://localhost:9000/Account/SignIn

5. 配置Nginx反向代理

在生产环境中,通常需要使用Nginx反向代理将前端应用的API请求转发到后端服务。这一步配置至关重要,可以解决跨域问题并提供统一的访问入口。

5.1 查找Docker容器IP

首先需要获取后端服务容器的IP地址:

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' backend-container

或者通过Docker Desktop图形界面查看容器详情。

5.2 修改Nginx配置实现反向代理

更新前端容器中的Nginx配置文件:

server {
    listen 80;
    server_name localhost;
    
    # 静态资源配置
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    # API代理配置 - 通用API路径
    location /api/ {    
        rewrite  /api/(.*)  /$1  break;  # 路径重写
        proxy_pass http://172.17.0.3:1000;  # 后端服务容器的IP和端口
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 特定接口路径
    location /Account/ { 
        proxy_pass http://172.17.0.3:1000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # 错误页面配置
    error_page  404              /404.html;
    error_page  500 502 503 504  /50x.html;

    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

5.3 应用新的Nginx配置

有两种方法可以应用新的Nginx配置:

方法1:进入容器修改配置

# 进入前端容器
docker exec -it frontend-container bash

# 编辑配置文件
vi /etc/nginx/conf.d/default.conf

# 测试Nginx配置是否正确
nginx -t

# 重新加载Nginx配置
nginx -s reload

如果容器内没有vi编辑器,可以先安装:

# Debian/Ubuntu系统
apt-get update && apt-get install -y vim

# Alpine系统
apk add --no-cache vim

方法2:重建前端容器

如果不想直接修改容器内的文件,可以更新本地的Nginx配置文件,然后重新构建前端镜像:

  1. 在项目中修改nginx/default.conf
  2. 重新构建前端镜像:docker build -t frontend-app:v2 .
  3. 停止并删除旧容器:docker stop frontend-container && docker rm frontend-container
  4. 运行新容器:docker run -d -p 3000:80 --name frontend-container frontend-app:v2

5.4 改进方案:使用Docker网络

上述方法中直接使用容器IP存在问题,因为容器重启后IP可能会变化。更好的做法是使用Docker网络:

# 创建Docker网络
docker network create app-network

# 运行后端容器,连接到网络
docker run -d --name backend-container --network app-network backend-service:v1

# 运行前端容器,连接到网络
docker run -d -p 3000:80 --name frontend-container --network app-network frontend-app:v1

然后在Nginx配置中,可以直接使用容器名称作为主机名:

location /api/ {
    rewrite /api/(.*) /$1 break;
    proxy_pass http://backend-container:1000;
    # ...其他配置...
}

6. 使用Docker Compose简化部署

为了简化多容器应用的部署和管理,可以使用Docker Compose。在项目根目录创建docker-compose.yml文件:

version: '3'

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "80:80"
    depends_on:
      - backend
    networks:
      - app-network

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    environment:
      - NODE_ENV=production
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

使用Docker Compose可以一键启动整个应用:

docker-compose up -d

停止并移除所有容器:

docker-compose down

7. 常用Docker命令速查表

命令 说明
docker build -t <镜像名>:<标签> . 构建镜像
docker run -d -p <主机端口>:<容器端口> --name <容器名> <镜像名> 创建并启动容器
docker ps 查看运行中的容器
docker ps -a 查看所有容器(包括已停止)
docker images 查看所有镜像
docker stop <容器ID/名称> 停止容器
docker start <容器ID/名称> 启动已停止的容器
docker rm <容器ID/名称> 删除容器
docker rmi <镜像ID/名称> 删除镜像
docker exec -it <容器ID/名称> bash 进入容器内部
docker logs <容器ID/名称> 查看容器日志
docker network create <网络名称> 创建Docker网络
docker network ls 列出所有网络
docker-compose up -d 启动所有服务(后台运行)
docker-compose down 停止并删除所有服务

8. 最佳实践与优化建议

8.1 镜像优化

  1. 使用多阶段构建:分离构建环境和运行环境,减小最终镜像体积
  2. 使用Alpine基础镜像:Alpine Linux基础镜像通常只有几MB大小
  3. 合理组织Dockerfile指令:充分利用Docker的缓存机制
  4. 减少镜像层数:合并RUN指令,减少层数
  5. 使用.dockerignore排除不必要文件

8.2 安全与访问控制

  1. 不要在容器中运行应用为root用户
  2. 使用环境变量管理敏感信息
  3. 定期更新基础镜像以修补安全漏洞
  4. 限制容器资源使用

8.3 持久化数据

对于需要持久化的数据(如数据库、上传文件等),应使用Docker卷(volumes):

# 创建卷
docker volume create data-volume

# 使用卷运行容器
docker run -v data-volume:/app/data -d my-image

或在docker-compose.yml中:

services:
  app:
    # ...其他配置...
    volumes:
      - data-volume:/app/data

volumes:
  data-volume:

9. CI/CD整合与自动化部署

将Docker与CI/CD工具(如GitHub Actions、GitLab CI、Jenkins)结合,可以实现代码提交后自动构建、测试和部署。

简单的GitHub Actions工作流示例:

name: Docker Build and Deploy

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
          
      - name: Build and push Docker image
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: username/app:latest

总结

本教程详细介绍了如何使用Docker部署前后端分离的Web应用,涵盖了Docker的基本概念、镜像构建、容器运行、网络配置等核心内容。通过容器化技术,可以显著提高应用的可移植性、可维护性和部署效率。

虽然对于纯前端开发来说,Docker可能不是日常必备技能,但随着DevOps理念的普及和全栈开发的趋势,了解并掌握Docker已成为现代开发者的基本素养。希望本教程能帮助您理解Docker的核心概念并在实际项目中应用容器化技术。

通过实践和不断优化,Docker不仅能简化您的部署流程,还能为应用提供更加稳定和一致的运行环境。