将 Yii 应用部署到 Docker Swarm 和 Caddy
本指南将引导你从空白服务器开始,使用 Caddy 作为反向代理,从容器注册表(Forgejo 或 Gitea)部署 Yii 应用程序到 Docker Swarm。
graph LR
A[Internet] --> B[Reverse Proxy: Caddy]
B --> C[app1.example.com]
B --> D[app2.example.com]
subgraph Docker Swarm Cluster
B
C
D
end前提条件
- 一台全新安装 Linux 发行版的服务器(推荐 Ubuntu 22.04 LTS 或更高版本)
- 指向服务器 IP 地址的域名
- 对服务器的 SSH 访问权限
- Docker 和命令行工具的基础知识
服务器准备
安装 Docker
有关安装说明,请参阅官方 Docker 文档。
初始化 Docker Swarm
将服务器初始化为 Docker Swarm 管理器:
docker swarm init --advertise-addr <YOUR_SERVER_IP>将 <YOUR_SERVER_IP> 替换为服务器的公共 IP 地址。
设置反向代理网络
创建一个专用的覆盖网络,供反向代理与服务通信:
docker network create --driver=overlay reverse_proxy_public设置 Caddy 作为反向代理
要将 Caddy 部署为反向代理,请创建文件 caddy-stack.yml:
services:
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- caddy_data:/data
networks:
- reverse_proxy_public
volumes:
caddy_data:
networks:
reverse_proxy_public:
external: true部署 Caddy:
docker stack deploy -c caddy-stack.yml caddyCaddy 会自动发现带有 Caddy 标签的服务,并使用 Let's Encrypt 设置 HTTPS。Yii3 应用程序模板默认使用 Caddy 标签:
deploy:
labels:
- caddy: ${PROD_HOST:-app.example.com}
- caddy.reverse_proxy: "{{upstreams 80}}"IMPORTANT
Make sure your domain DNS records are configured and pointing to your server before deploying services with Caddy labels, as Let's Encrypt requires domain validation.
设置容器注册表
你需要一个容器注册表来存储 Docker 镜像。选择以下选项之一。
选项 1:使用 Forgejo
要部署 Forgejo,请创建文件 forgejo-stack.yml:
services:
forgejo:
image: codeberg.org/forgejo/forgejo:1.21
ports:
- "3000:3000"
volumes:
- forgejo_data:/data
networks:
- reverse_proxy_public
deploy:
labels:
- "caddy=git.example.com"
- "caddy.reverse_proxy={{upstreams 3000}}"
volumes:
forgejo_data:
networks:
reverse_proxy_public:
external: true部署 Forgejo:
docker stack deploy -c forgejo-stack.yml forgejo将 git.example.com 替换为所需的子域名。
部署后,访问 https://git.example.com 上的 Forgejo 并完成初始设置。确保在设置中启用容器注册表。
选项 2:使用 Gitea
要部署 Gitea,请创建文件 gitea-stack.yml:
services:
gitea:
image: gitea/gitea:latest
ports:
- "3000:3000"
volumes:
- gitea_data:/data
networks:
- reverse_proxy_public
deploy:
labels:
- "caddy=git.example.com"
- "caddy.reverse_proxy={{upstreams 3000}}"
volumes:
gitea_data:
networks:
reverse_proxy_public:
external: true部署 Gitea:
docker stack deploy -c gitea-stack.yml gitea将 git.example.com 替换为所需的子域名。
部署后,访问 https://git.example.com 上的 Gitea 并完成初始设置。确保在设置中启用容器注册表。
配置 Yii 应用程序
更新 Makefile 配置
Yii 应用程序模板包含一个带有部署命令的 Makefile。更新项目中的 docker/.env 文件:
STACK_NAME=myapp
#
# Production
#
PROD_HOST=app.example.com
PROD_SSH="ssh://user@your-server-ip"
IMAGE=git.example.com/username/myapp
IMAGE_TAG=latest替换以下值:- STACK_NAME:应用程序堆栈的唯一名称 - PROD_HOST:应用程序可访问的域名 - PROD_SSH:到服务器的 SSH 连接字符串(格式:ssh://user@host)- IMAGE:注册表中容器镜像的完整路径 - IMAGE_TAG:镜像标签,通常为 latest 或版本号
配置生产环境
使用生产环境变量更新 docker/prod/.env:
APP_ENV=prod
YII_DEBUG=false
YII_ENV=prod
# Database configuration
DB_HOST=db
DB_NAME=myapp
DB_USER=myapp
DB_PASSWORD=secure_password_here
# Add other environment-specific variablesWARNING
切勿将敏感凭据提交到版本控制。对敏感值使用 docker/prod/override.env 并将其添加到 .gitignore。
查看生产环境 Docker Compose 配置
默认的 docker/prod/compose.yml 包括:
services:
app:
image: ${IMAGE}:${IMAGE_TAG}
networks:
- reverse_proxy_public
volumes:
- runtime:/app/runtime
- caddy_data:/data
- caddy_config:/config
env_file:
- path: ./prod/.env
- path: ./prod/override.env
required: false\
environment:
CADDY_EXTRA_CONFIG: 'auto_https off'
deploy:
replicas: 2
update_config:
delay: 10s
parallelism: 1
order: start-first
failure_action: rollback
monitor: 10s
rollback_config:
parallelism: 0
order: stop-first
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
labels:
caddy: ${PROD_HOST:-app.example.com}
caddy.reverse_proxy: "{{upstreams 80}}"此配置:
- 运行 2 个副本以实现高可用性
- 使用滚动更新策略,失败时自动回滚
- 配置
labels以在反向代理上自动启用 HTTPS - 禁用容器本身获取 HTTPS 证书 因为代理通过 HTTP 与容器通信。即
auto_https off。
如果需要数据库,请将其添加到堆栈中:
services:
app:
# ... existing configuration ...
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- db_data:/var/lib/postgresql/data
networks:
- reverse_proxy_public
deploy:
placement:
constraints:
- node.role == manager
secrets:
- db_password
volumes:
runtime:
db_data:
secrets:
db_password:
external: true在服务器上创建数据库 password secret:
echo "your_secure_password" | docker secret create db_password -构建并推送镜像
在本地机器上设置 Docker 登录
配置 Docker 以向容器注册表进行身份验证:
docker login git.example.com在提示时输入用户名和 password。
构建生产镜像
使用 Makefile 构建生产镜像:
make prod-build这将运行 Makefile 中定义的命令:
docker build --file docker/Dockerfile --target prod --pull -t ${IMAGE}:${IMAGE_TAG} .Dockerfile 使用多阶段构建:1. 在构建器阶段安装 Composer 依赖项 2. 创建仅包含必要文件的最小生产镜像 3. 以非 root 用户(www-data)身份运行
将镜像推送到注册表
将构建的镜像推送到容器注册表:
make prod-push这将执行:
docker push ${IMAGE}:${IMAGE_TAG}部署到 Docker Swarm
Configure SSH access
Set up SSH key-based authentication to your server:
# Generate SSH key (if you don't have one)
ssh-keygen -t ed25519 -C "your_email@example.com"
# Copy the key to your server
ssh-copy-id user@your-server-ip
# Add the SSH host to your SSH config (~/.ssh/config)
cat >> ~/.ssh/config << EOF
Host docker-web
HostName your-server-ip
User user
IdentityFile ~/.ssh/id_ed25519
EOF设置 Docker 上下文
创建用于远程部署的 Docker 上下文:
docker context create swarm-prod --docker "host=ssh://docker-web"或者,配置 DOCKER_HOST 环境变量:
export DOCKER_HOST=ssh://docker-web部署应用程序
将应用程序堆栈部署到 Docker Swarm:
make prod-deploy这将执行:
docker -H ${PROD_SSH} stack deploy --prune --detach=false --with-registry-auth -c docker/compose.yml -c docker/prod/compose.yml ${STACK_NAME}--with-registry-auth 标志确保 Swarm 节点可以从私有注册表拉取镜像。
验证部署
检查服务状态:
docker -H ssh://docker-web service ls
docker -H ssh://docker-web service ps ${STACK_NAME}_app查看日志:
docker -H ssh://docker-web service logs ${STACK_NAME}_app监控和维护
查看服务日志
# View all logs
docker -H ssh://docker-web service logs -f ${STACK_NAME}_app
# View logs from the last 100 lines
docker -H ssh://docker-web service logs --tail 100 ${STACK_NAME}_app
# View logs with timestamps
docker -H ssh://docker-web service logs -t ${STACK_NAME}_app扩展应用程序
调整副本数量:
docker -H ssh://docker-web service scale ${STACK_NAME}_app=3或者更新 docker/prod/compose.yml 中的 replicas 值并重新部署。
资源限制
添加资源限制以防止容器消耗所有服务器资源。更新 docker/prod/compose.yml:
services:
app:
# ... existing configuration ...
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M安全注意事项
对敏感数据使用 Docker secrets
对于敏感信息,使用 Docker secrets 而不是环境变量:
# 创建 secrets
echo "database_password" | docker secret create db_password -
echo "api_key" | docker secret create api_key -更新 docker/prod/compose.yml:
services:
app:
secrets:
- db_password
- api_key
secrets:
db_password:
external: true
api_key:
external: true在应用程序中通过 /run/secrets/secret_name 访问 secrets。
设置防火墙
在服务器上配置 UFW(简单防火墙):
# Allow SSH
sudo ufw allow 22/tcp
# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow Docker Swarm ports (if you plan to add more nodes)
sudo ufw allow 2377/tcp
sudo ufw allow 7946/tcp
sudo ufw allow 7946/udp
sudo ufw allow 4789/udp
# Enable the firewall
sudo ufw enable保持系统更新
定期更新服务器和 Docker:
# 更新系统包
sudo apt-get update && sudo apt-get upgrade -y
# 更新 Docker 镜像
docker -H ssh://docker-web service update --image ${IMAGE}:${IMAGE_TAG} ${STACK_NAME}_app故障排除
服务无法启动
检查服务事件和日志:
docker -H ssh://docker-web service ps ${STACK_NAME}_app --no-trunc
docker -H ssh://docker-web service logs ${STACK_NAME}_app常见问题:- 镜像拉取错误:使用 docker -H ssh://docker-web login 验证注册表身份验证 - 端口冲突:确保没有其他服务使用端口 80/443 - 资源约束:使用 docker -H ssh://docker-web node ls 检查可用资源
SSL 证书问题
如果 Caddy 无法获取证书:- 验证 DNS 是否指向服务器 - 检查端口 80 和 443 是否可从互联网访问 - 确保 Let's Encrypt 配置中的电子邮件有效 - 检查日志:docker -H ssh://docker-web service logs caddy
容器注册表连接问题
测试注册表连接:
# 从本地机器
docker pull git.example.com/username/myapp:latest
# 从服务器
docker -H ssh://docker-web pull git.example.com/username/myapp:latest总结
你已成功将 Yii 应用程序部署到 Docker Swarm,包括:- 容器注册表(Forgejo 或 Gitea)- 通过 Caddy 自动启用 HTTPS - 通过滚动更新实现零停机部署 - 通过多个副本实现高可用性
Makefile 命令简化了部署工作流程:- make prod-build - 构建生产镜像 - make prod-push - 推送到注册表 - make prod-deploy - 部署到 Docker Swarm
有关更多信息,请参阅:- Yii 应用程序模板 - Docker Swarm 文档 - Caddy Docker Proxy