使用 Docker 部署网站的完整指南:从开发到生产
"代码在我电脑上跑得好好的,怎么一部署就出问题?"几乎每个亲手部署过网站的人都听过这句话。问题往往不在代码本身,而在于环境:本地的 Node 版本、系统依赖、数据库配置和服务器上的完全不同。Docker 的解决思路,是把「运行环境」本身也变成代码——提交到仓库、随处复现、随时回滚。Docker 容器化让网站部署从「手工配置」转变为「代码化管理」。这篇指南不讨论抽象概念,直接用 前端 + 后端 + 数据库 的完整项目,带你从 Dockerfile 一路走到生产部署。
项目结构
一个典型的容器化网站项目长这样:
project/
├── frontend/ # 前端应用(Nginx + 静态文件)
├── backend/ # 后端 API(Node.js/Python/Go)
├── docker-compose.yml
└── .env
代码按职责拆成 frontend 和 backend 两个目录,各自维护一份 Dockerfile,前端构建、后端服务、数据库就能独立扩容、独立更新。.env 集中存放数据库密码、密钥等敏感信息,并被 .gitignore 排除,避免机密进入版本库。
前端 Dockerfile:多阶段构建
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
多阶段构建是前端镜像缩身的关键。第一阶段 builder 用 node:20-alpine 安装依赖并执行 npm run build;第二阶段 nginx:alpine 只从构建阶段拷贝产物。最终镜像里既没有源码也没有 node_modules,体积能从 1GB+ 压缩到几十 MB,依赖少了,安全漏洞面也随之减小。npm ci 比 npm install 更适合 CI:它严格按照 package-lock.json 安装,保证本地与线上依赖完全一致。这份镜像还能直接用于本地联调,省去反复安装环境的时间。
后端 Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
后端生产镜像不需要 devDependencies,npm ci --production 只装运行时依赖。如果你用的是 Python,把 RUN npm ci --production 换成 RUN pip install --no-cache-dir -r requirements.txt 即可,思路完全一样:锁版本、只装生产依赖、用非 root 用户运行进程(Dockerfile 里加一行 USER node)。
另外别忘了准备一份 .dockerignore,把 node_modules、dist、.git、*.log 排除在构建上下文之外。它有两个作用:一是避免本地依赖被错误地带进镜像,二是大幅减少发送给 Docker 守护进程的文件量,让构建更快。
Docker Compose 编排
version: "3.8"
services:
nginx:
build: ./frontend
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- backend
backend:
build: ./backend
env_file: .env
environment:
- NODE_ENV=production
restart: always
postgres:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
env_file: .env
volumes:
pgdata:
Compose 把三个服务串成一台"虚拟服务器"。几个细节值得注意:volumes: pgdata: 用命名卷持久化数据库,容器删了重建数据还在;env_file: .env 把环境变量统一注入;restart: always 让进程崩溃或服务器重启后自动拉起。前后端绑定到同一网络后,Nginx 里直接用服务名 backend:3000 访问即可,不用关心容器的 IP 地址。
配置反向代理
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
location / {
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static/ {
alias /usr/share/nginx/html/static/;
expires 365d;
}
}
80 端口一律 301 跳转到 HTTPS,静态资源加 expires 365d 走浏览器缓存,动态请求反代到后端。SSL 证书可以用 certbot 自动续期:certbot certonly --webroot -w /usr/share/nginx/html -d example.com,再把证书目录挂载进容器。
接入 CI/CD
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker push myapp:${{ github.sha }}
- name: Deploy to server
run: |
ssh user@server "cd /app && docker-compose pull && docker-compose up -d"
CI/CD 的价值在于把"构建—推送—部署"变成一次 git push 自动完成的流水线。镜像用 commit SHA 打标签,回滚时只需 docker-compose up -d 指回上一个标签,几秒钟就能完成。
健康检查与优雅退出
生产环境不能只看"容器起来了",还要确认服务真的可用。给后端加一个健康检查接口,并在 Compose 里声明:
backend:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
Nginx 的 depends_on 默认只等容器启动,配合 healthcheck 才能等"服务就绪"。restart: always 配合健康检查,可以让服务挂了之后自动拉起,而不是把请求继续转发给一个半死不活的后端。
环境变量与密钥管理
env_file: .env 解决的是"配置不进镜像"的问题:镜像里不写死数据库密码,运行时从环境变量注入。实践建议:.env 永远不进 Git;dev/staging/prod 各维护一份 .env;敏感值不要在 docker-compose.yml 里明文写。需要更严格的安全要求时,可以引入 Docker secrets 或云厂商的密钥管理服务,把密钥生命周期管起来。这些做法合起来,就是让镜像可移植、配置可替换、密钥可审计。
常见问题排查
- 容器启动即退出:先看日志
docker logs <container>,再确认 CMD 是否前台运行。Nginx 需要daemon off;,否则容器会立刻退出。 - 端口冲突:
docker ps查看已占用端口,或改 Compose 里ports左侧的宿主机端口。 - 数据丢失:确认数据库用了命名卷,不要在容器内写重要数据。
- 构建缓慢:把
package*.json单独COPY并先RUN npm ci,利用 Docker 层缓存,源码改动不会触发依赖重装。
16IDC 观察
Docker 化部署极大减少了环境不一致导致的问题。建议新项目从一开始就使用 Docker 和 Docker Compose 管理部署流程,即使是一个简单的静态网站,Docker 也能提供更清晰的发布和回滚流程。容器时代的部署,拼的不是单点脚本,而是这套可重复的"构建—编排—监控"链路。镜像构建好之后,还需要一台合适的服务器来承载,可以参考我们的 服务器推荐。
参考:Docker 官方文档 https://docs.docker.com/build/building/multi-stage/;Docker Compose 文档 https://docs.docker.com/compose/