Docker 部署 #

Docker 部署概述 #

Heroku 支持使用 Docker 容器部署应用,这为开发者提供了更灵活的运行时环境控制。

Docker vs Buildpack #

特性 Buildpack Docker
配置复杂度
环境控制 有限 完全控制
本地一致性 一般
依赖管理 自动 手动配置
适用场景 标准应用 定制需求

Container Registry #

登录 Container Registry #

bash
# 登录 Heroku Container Registry
heroku container:login

# 输出示例
# Login Succeeded

推送镜像 #

bash
# 构建并推送镜像
heroku container:push web

# 推送特定进程类型
heroku container:push worker

# 推送所有进程类型
heroku container:push --recursive

# 推送到指定应用
heroku container:push web -a myapp

发布镜像 #

bash
# 发布镜像
heroku container:release web

# 发布多个进程类型
heroku container:release web worker

# 推送并发布
heroku container:push web && heroku container:release web

Dockerfile 编写 #

基本结构 #

dockerfile
# 使用官方 Node.js 镜像
FROM node:20-alpine

# 设置工作目录
WORKDIR /app

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

# 安装依赖
RUN npm ci --only=production

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE $PORT

# 启动命令
CMD ["node", "index.js"]

多进程 Dockerfile #

dockerfile
# Dockerfile.web
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE $PORT
CMD ["node", "server.js"]

# Dockerfile.worker
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "worker.js"]

多阶段构建 #

dockerfile
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 生产阶段
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE $PORT
CMD ["node", "dist/main.js"]

heroku.yml #

基本配置 #

yaml
# heroku.yml
build:
  docker:
    web: Dockerfile
    worker: Dockerfile.worker

完整配置示例 #

yaml
# heroku.yml
setup:
  addons:
    - plan: heroku-postgresql:mini
    - plan: heroku-redis:mini

build:
  docker:
    web: Dockerfile.web
    worker: Dockerfile.worker

run:
  web: node server.js
  worker: node worker.js

配置说明 #

字段 说明
setup.addons 自动创建 Add-ons
build.docker 指定 Dockerfile
run 定义启动命令

部署流程 #

标准部署流程 #

bash
# 1. 创建应用
heroku create myapp

# 2. 设置堆栈为 container
heroku stack:set container

# 3. 创建 heroku.yml
cat > heroku.yml << 'EOF'
build:
  docker:
    web: Dockerfile
EOF

# 4. 推送部署
git add heroku.yml
git commit -m "Add heroku.yml"
git push heroku main

Container Registry 部署流程 #

bash
# 1. 登录 Container Registry
heroku container:login

# 2. 创建应用
heroku create myapp

# 3. 构建并推送镜像
heroku container:push web -a myapp

# 4. 发布镜像
heroku container:release web -a myapp

# 5. 查看日志
heroku logs --tail -a myapp

部署流程图 #

text
┌─────────────────────────────────────────────────────┐
│              Docker 部署流程                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  方式一:Git + heroku.yml                           │
│  ┌─────────────────────────────────────────────┐   │
│  │  Dockerfile + heroku.yml                     │   │
│  │           │                                  │   │
│  │           ▼                                  │   │
│  │  git push heroku main                        │   │
│  │           │                                  │   │
│  │           ▼                                  │   │
│  │  Heroku 构建镜像并部署                        │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
│  方式二:Container Registry                         │
│  ┌─────────────────────────────────────────────┐   │
│  │  本地 Dockerfile                             │   │
│  │           │                                  │   │
│  │           ▼                                  │   │
│  │  heroku container:push web                   │   │
│  │           │                                  │   │
│  │           ▼                                  │   │
│  │  heroku container:release web                │   │
│  └─────────────────────────────────────────────┘   │
│                                                     │
└─────────────────────────────────────────────────────┘

不同语言示例 #

Node.js #

dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE $PORT

CMD ["node", "index.js"]

Python #

dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE $PORT

CMD ["gunicorn", "--bind", "0.0.0.0:$PORT", "app:app"]

Java #

dockerfile
FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

COPY target/myapp.jar .

EXPOSE $PORT

CMD ["java", "-jar", "myapp.jar"]

Go #

dockerfile
FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o main .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .

EXPOSE $PORT

CMD ["./main"]

Ruby #

dockerfile
FROM ruby:3.3-alpine

RUN apk add --no-cache build-base postgresql-dev

WORKDIR /app

COPY Gemfile Gemfile.lock ./
RUN bundle install --without development test

COPY . .

EXPOSE $PORT

CMD ["rails", "server", "-b", "0.0.0.0", "-p", "$PORT"]

环境变量处理 #

在 Dockerfile 中使用环境变量 #

dockerfile
FROM node:20-alpine

# 设置默认环境变量
ENV NODE_ENV=production

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

# Heroku 会注入 PORT 环境变量
EXPOSE $PORT

CMD ["node", "index.js"]

启动脚本处理环境变量 #

dockerfile
FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

COPY start.sh /start.sh
RUN chmod +x /start.sh

EXPOSE $PORT

CMD ["/start.sh"]
bash
#!/bin/sh
# start.sh

echo "Starting application..."
echo "PORT: $PORT"
echo "NODE_ENV: $NODE_ENV"

exec node index.js

多进程应用 #

heroku.yml 配置 #

yaml
build:
  docker:
    web: Dockerfile.web
    worker: Dockerfile.worker
    clock: Dockerfile.clock

run:
  web: node server.js
  worker: node worker.js
  clock: node scheduler.js

推送多进程镜像 #

bash
# 推送所有进程类型
heroku container:push --recursive

# 推送特定进程类型
heroku container:push web worker

# 发布所有进程类型
heroku container:release web worker clock

镜像管理 #

查看镜像 #

bash
# 查看本地镜像
docker images

# 查看 Heroku 镜像
docker images registry.heroku.com/myapp/web

镜像标签 #

bash
# 标签镜像
docker tag myapp:latest registry.heroku.com/myapp/web

# 推送标签
docker push registry.heroku.com/myapp/web

清理镜像 #

bash
# 删除本地镜像
docker rmi registry.heroku.com/myapp/web

# 清理未使用的镜像
docker image prune

本地开发 #

本地运行容器 #

bash
# 构建镜像
docker build -t myapp .

# 运行容器
docker run -p 3000:3000 -e PORT=3000 myapp

# 使用环境变量文件
docker run -p 3000:3000 --env-file .env myapp

Docker Compose #

yaml
# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - PORT=3000
      - DATABASE_URL=postgres://postgres:postgres@db:5432/myapp
    depends_on:
      - db
      - redis

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_PASSWORD=postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

volumes:
  postgres_data:
bash
# 启动服务
docker-compose up

# 后台运行
docker-compose up -d

# 停止服务
docker-compose down

最佳实践 #

1. 优化镜像大小 #

dockerfile
# 使用 Alpine 基础镜像
FROM node:20-alpine

# 多阶段构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]

2. 安全最佳实践 #

dockerfile
# 使用非 root 用户
FROM node:20-alpine

RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

WORKDIR /app

COPY --chown=nextjs:nodejs . .

USER nextjs

CMD ["node", "index.js"]

3. 健康检查 #

dockerfile
FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

EXPOSE $PORT

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:$PORT/health || exit 1

CMD ["node", "index.js"]

故障排查 #

镜像推送失败 #

bash
# 检查登录状态
heroku container:login

# 检查镜像名称
docker images | grep heroku

# 手动推送
docker push registry.heroku.com/myapp/web

应用启动失败 #

bash
# 查看日志
heroku logs --tail

# 检查环境变量
heroku config

# 本地测试
docker run -e PORT=3000 -p 3000:3000 myapp

端口绑定问题 #

javascript
// 确保使用 PORT 环境变量
const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
  console.log(`Server listening on port ${PORT}`);
});

下一步 #

Docker 部署掌握后,接下来学习 多环境管理 了解如何管理开发、测试、生产环境!

最后更新:2026-03-28