Deploying Yii applications to Docker Swarm
This guide walks you through deploying a Yii application to Docker Swarm from a blank server, using Caddy as a reverse proxy and a container registry (Forgejo or Gitea).
graph LR
A[Internet] --> B[Reverse Proxy: Caddy or Traefik]
B --> C[app1.example.com]
B --> D[app2.example.com]
subgraph Docker Swarm Cluster
B
C
D
endЧто понадобится перед чтением?
- A server with a fresh installation of a Linux distribution (Ubuntu 22.04 LTS or later recommended)
- A domain name pointing to your server's IP address
- SSH access to your server
- Basic knowledge of Docker and command-line tools
Server preparation
Install Docker
For installation instructions, see the official Docker documentation.
Initialize Docker Swarm
Initialize your server as a Docker Swarm manager:
docker swarm init --advertise-addr <YOUR_SERVER_IP>Replace <YOUR_SERVER_IP> with your server's public IP address.
Set up the Caddy network
Create a dedicated overlay network for Caddy to communicate with your services:
docker network create --driver=overlay caddy_publicSetting up a container registry
You need a container registry to store your Docker images. Choose one of the following options.
Option 1: Using Forgejo
Deploy Forgejo as a container registry.
Create a file forgejo-stack.yml:
version: '3.8'
services:
forgejo:
image: codeberg.org/forgejo/forgejo:1.21
ports:
- "3000:3000"
volumes:
- forgejo_data:/data
networks:
- caddy_public
deploy:
labels:
- "caddy=git.example.com"
- "caddy.reverse_proxy={{upstreams 3000}}"
volumes:
forgejo_data:
networks:
caddy_public:
external: trueDeploy Forgejo:
docker stack deploy -c forgejo-stack.yml forgejoReplace git.example.com with your desired subdomain.
After deployment, access Forgejo at https://git.example.com and complete the initial setup. Make sure to enable the container registry in the settings.
Option 2: Using Gitea
Deploy Gitea as a container registry.
Create a file gitea-stack.yml:
version: '3.8'
services:
gitea:
image: gitea/gitea:latest
ports:
- "3000:3000"
volumes:
- gitea_data:/data
networks:
- caddy_public
deploy:
labels:
- "caddy=git.example.com"
- "caddy.reverse_proxy={{upstreams 3000}}"
volumes:
gitea_data:
networks:
caddy_public:
external: trueDeploy Gitea:
docker stack deploy -c gitea-stack.yml giteaReplace git.example.com with your desired subdomain.
After deployment, access Gitea at https://git.example.com and complete the initial setup. Make sure to enable the container registry in the settings.
Setting up Caddy as reverse proxy
The Yii application template includes Caddy labels by default. Deploy Caddy with automatic HTTPS.
Create a file caddy-stack.yml:
version: '3.8'
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:
- caddy_public
volumes:
caddy_data:
networks:
caddy_public:
external: trueDeploy Caddy:
docker stack deploy -c caddy-stack.yml caddyCaddy automatically discovers services with Caddy labels and sets up HTTPS using Let's Encrypt.
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.
Alternative: Using Traefik as reverse proxy
If you prefer Traefik over Caddy, deploy it as follows:
Create a file traefik-stack.yml:
version: '3.8'
services:
traefik:
image: traefik:v2.10
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.swarmMode=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "traefik_certificates:/letsencrypt"
networks:
- caddy_public
deploy:
placement:
constraints:
- node.role == manager
volumes:
traefik_certificates:
networks:
caddy_public:
external: trueDeploy Traefik:
docker stack deploy -c traefik-stack.yml traefikIf using Traefik, you'll need to modify the Yii application's docker/prod/compose.yml to use Traefik labels instead of Caddy labels.
Example Traefik labels for your application:
deploy:
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
- "traefik.http.services.app.loadbalancer.server.port=80"Configuring your Yii application
Update the Makefile configuration
The Yii application template includes a Makefile with deployment commands. Update the docker/.env file in your project:
STACK_NAME=myapp
#
# Production
#
PROD_HOST=app.example.com
PROD_SSH="ssh://user@your-server-ip"
IMAGE=git.example.com/username/myapp
IMAGE_TAG=latestReplace the values: - STACK_NAME: A unique name for your application stack
PROD_HOST: The domain name where your app will be accessible -PROD_SSH: SSH connection string to your server (format:ssh://user@host)IMAGE: Full path to your container image in the registry -IMAGE_TAG: Image tag, typicallylatestor a version number
Configure the production environment
Update docker/prod/.env with your production environment variables:
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
Never commit sensitive credentials to version control. Use docker/prod/override.env for sensitive values and add it to .gitignore.
Review the production Docker Compose configuration
The default docker/prod/compose.yml includes:
services:
app:
image: ${IMAGE}:${IMAGE_TAG}
networks:
- caddy_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}}"This configuration:
- Runs 2 replicas for high availability
- Uses a rolling update strategy with automatic rollback on failure
- Configures
labelsfor automatic HTTPS on the reverse proxy - Disables obtaining of HTTPs certificates on the container itself since proxy communicates with the container via HTTP. That is
auto_https off.
If you need a database, add it to the stack:
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:
- caddy_public
deploy:
placement:
constraints:
- node.role == manager
secrets:
- db_password
volumes:
runtime:
db_data:
secrets:
db_password:
external: trueCreate the database password secret on the server:
echo "your_secure_password" | docker secret create db_password -Building and pushing the image
Set up Docker login on your local machine
Configure Docker to authenticate with your container registry:
docker login git.example.comEnter your username and password when prompted.
Build the production image
Use the Makefile to build your production image:
make prod-buildThis runs the command defined in the Makefile:
docker build --file docker/Dockerfile --target prod --pull -t ${IMAGE}:${IMAGE_TAG} .The Dockerfile uses a multi-stage build: 1. Installs Composer dependencies in a builder stage 2. Creates a minimal production image with only the necessary files 3. Runs as a non-root user (www-data)
Push the image to the registry
Push your built image to the container registry:
make prod-pushThis executes:
docker push ${IMAGE}:${IMAGE_TAG}Deploying to 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
EOFSet up Docker context
Create a Docker context for remote deployment:
docker context create swarm-prod --docker "host=ssh://docker-web"Alternatively, configure the DOCKER_HOST environment variable:
export DOCKER_HOST=ssh://docker-webDeploy the application
Deploy your application stack to Docker Swarm:
make prod-deployThis executes:
docker -H ${PROD_SSH} stack deploy --with-registry-auth -c docker/compose.yml -c docker/prod/compose.yml ${STACK_NAME}The --with-registry-auth flag ensures the Swarm nodes can pull images from your private registry.
Verify the deployment
Check the status of your services:
docker -H ssh://docker-web service ls
docker -H ssh://docker-web service ps ${STACK_NAME}_appView logs:
docker -H ssh://docker-web service logs ${STACK_NAME}_appMonitoring and maintenance
View service logs
# 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}_appScale the application
Adjust the number of replicas:
docker -H ssh://docker-web service scale ${STACK_NAME}_app=3Or update the replicas value in docker/prod/compose.yml and redeploy.
Resource limits
Add resource limits to prevent containers from consuming all server resources. Update docker/prod/compose.yml:
services:
app:
# ... existing configuration ...
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256MSecurity considerations
Use Docker secrets for sensitive data
Instead of environment variables, use Docker secrets for sensitive information:
# Create secrets
echo "database_password" | docker secret create db_password -
echo "api_key" | docker secret create api_key -Update docker/prod/compose.yml:
services:
app:
secrets:
- db_password
- api_key
secrets:
db_password:
external: true
api_key:
external: trueAccess secrets in your application at /run/secrets/secret_name.
Set up a firewall
Configure UFW (Uncomplicated Firewall) on your server:
# 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 enableKeep the system updated
Regularly update your server and Docker:
# Update system packages
sudo apt-get update && sudo apt-get upgrade -y
# Update Docker images
docker -H ssh://docker-web service update --image ${IMAGE}:${IMAGE_TAG} ${STACK_NAME}_appTroubleshooting
Service won't start
Check service events and logs:
docker -H ssh://docker-web service ps ${STACK_NAME}_app --no-trunc
docker -H ssh://docker-web service logs ${STACK_NAME}_appCommon issues: - Image pull errors: Verify registry authentication with docker -H ssh://docker-web login - Port conflicts: Ensure no other services are using ports 80/443 - Resource constraints: Check available resources with docker -H ssh://docker-web node ls
SSL certificate issues
If Caddy/Traefik can't obtain certificates: - Verify DNS is pointing to your server - Check that ports 80 and 443 are accessible from the internet - Ensure the email in the Let's Encrypt configuration is valid - Check logs: docker -H ssh://docker-web service logs caddy or traefik_traefik
Container registry connection issues
Test registry connectivity:
# From your local machine
docker pull git.example.com/username/myapp:latest
# From the server
docker -H ssh://docker-web pull git.example.com/username/myapp:latestSummary
You've successfully deployed a Yii application to Docker Swarm with: - A container registry (Forgejo or Gitea) - Automatic HTTPS via Caddy or Traefik - Zero-downtime deployments with rolling updates - High availability with multiple replicas
The Makefile commands simplify the deployment workflow: - make prod-build
- Build the production image -
make prod-push- Push to the registry -make prod-deploy- Deploy to Docker Swarm
For more information, see: - Yii Application Template - Docker Swarm Documentation - Caddy Docker Proxy - Traefik Documentation