DevOps
Deploying to production
Create a new server instance
Spin up a new server using your cloud provider (link to Digital Ocean) and connect to your server instance via its public IP address
Configure UFW (Uncomplicated Firewall) to allow ports 22, 80 & 443
# Add rules
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
# Check what's configured
sudo ufw show added
# Enable the firewall
sudo ufw enable
Install Docker
Set up Docker's apt repository.
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Install Docker packages
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Allow non-root user to run docker
sudo usermod -aG docker ubuntu
You will need to log out and back in again, but once you are logged in again you should be able to run docker ps successfully as the ubuntu user.
Setup your site (single project)
Project Folder
Create the folder to house your site
# folder for the projects
sudo mkdir -p /data/sites
# Allow the ubuntu user write access to the /data folder
sudo chown ubuntu:ubuntu /data
then go into this directory
cd /opt/sites/supercoolwidgets.xyz
Multi-Project Server (multi project)
# folder for the docker compose services
sudo mkdir -p /data/services
Create a Docker compose file (/data/services/compose.yaml)
services:
traefik:
image: traefik:v3.4.3
container_name: traefik
restart: unless-stopped
networks:
- web
ports:
- "80:80"
- "443:443"
- "8080:8080" # optional dashboard
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_letsencrypt_data:/letsencrypt
command:
# ── discovery ───────────────────────────────────────────────
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
# ── entrypoints ─────────────────────────────────────────────
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.websecure.address=:443"
# ── ACME / Let’s Encrypt ────────────────────────────────────
- "--certificatesresolvers.le.acme.email=[... YOUR EMAIL ADDRESS ...]"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge=true"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
networks:
web:
external: true
volumes:
traefik_letsencrypt_data:
Note: The optional dashboard is secure as we don't expose port 8080 (only 80, 443 & 22). Later we will show how to access this dashboard without opening any other ports.
Create the web network
You only need to do this once
docker network create web
Start the traefik container
docker compose up -d
Project Setup
mkdir /data/sites/my-project
Add the compose.yaml
# /data/sites/my-project/compose.yml
name: ${APP_NAME}_prod
services:
caddy:
restart: unless-stopped
image: ${CI_REGISTRY_IMAGE}:caddy-${RELEASE}
#ports:
# - 80:80
# - 443:443
networks:
- web
- internal
volumes:
- caddy_data:/data
- caddy_config:/config
- app_public_storage:/app/public/uploads
labels:
- "traefik.enable=true"
- "traefik.http.routers.${APP_NAME}.rule=Host(`my-project.com`) || Host(`www.my-project.com`)"
- "traefik.http.routers.${APP_NAME}.entrypoints=websecure"
- "traefik.http.routers.${APP_NAME}.tls.certresolver=le"
- "traefik.http.services.${APP_NAME}.loadbalancer.server.port=80"
php:
restart: unless-stopped
image: ${CI_REGISTRY_IMAGE}:app-${RELEASE}
networks:
- internal
volumes:
- app_public_storage:/app/public/uploads
- app_private_storage:/app/storage
volumes:
caddy_data:
caddy_config:
app_public_storage:
app_private_storage:
networks:
web:
external: true
internal:
Add the .env file
# /data/sites/my-project/.env
APP_NAME=my-project
CI_REGISTRY_IMAGE=registry.gitlab.com/[USERNAME]/[REPOSITORY]
RELEASE=v1.6.0
Create a PAT so your server can retrieve the containers
Go to https://gitlab.com/-/user_settings/personal_access_tokens and create a personal access token to pull the containers. The PAT only needs the read_registry scope and if you are running a multi project server, you should only need a single PAT for all projects. (only need to set this up once).
Log in to the registry with your PAT
docker login https://registry.gitlab.com -u [USERNAME]
Point the DNS to the new server
??
Clear the cache
docker compose exec php bash -c "./bin/console cache:clear"
Start the project
docker compose up