Ajouter un nouveau projet sur un VPS déjà dockerisé (Caddy + Watchtower)

Procédure minimale pour ajouter un service supplémentaire à une infra existante : dossier, env, Compose, labels Caddy et DNS.

Written by AI
#Docker #Caddy #Watchtower #CI/CD #VPS #Production

Ce guide décrit la procédure minimale pour ajouter un nouveau projet à un VPS déjà configuré avec :

🎯 Objectif : déployer un nouveau service applicatif en réutilisant l'infra existante (sans retoucher Caddy frontal ni Watchtower).


1) Organisation des dossiers

Isoler chaque application dans son propre répertoire (lisibilité, déploiements indépendants) :

~/infra/            # Caddy frontal + Watchtower (déjà en place)
~/blog/             # projet existant
~/nouveau-projet/
└─ api/            # service à ajouter (ex: API)

2) Cloner le dépôt

cd ~
mkdir -p ~/nouveau-projet
cd ~/nouveau-projet
git clone git@github.com:<user>/<repo>.git api
cd api

3) Variables d'environnement

Créer un .env.prod local au serveur (non commité) :

cat > .env.prod <<'ENV'
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=$(php -r 'echo bin2hex(random_bytes(32));')
PORT=8080
# DB_*, JWT_*, etc. si nécessaire
ENV

chmod 600 .env.prod

Pour générer manuellement APP_SECRET :

php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'
# ou
openssl rand -hex 32

4) Docker Compose du service

Le Caddy frontal découvre le service via labels (caddy-docker-proxy). Le service expose son HTTP interne (ex. Caddy interne/FrankenPHP sur 8080), sans publier de port hôte.

# compose.prod.yaml
services:
  api:
    image: ghcr.io/<user>/<repo>:main
    restart: unless-stopped
    env_file:
      - .env.prod
    networks: [edge]                 # réseau partagé déjà utilisé par le Caddy frontal
    # expose: ["8080"]               # utile si l'image ne publie pas déjà 8080
    labels:
      # Watchtower: suivi explicite (voir article labels)
      com.centurylinklabs.watchtower.enable: "true"

      # Caddy frontal : domaine public -> proxy vers le port interne
      caddy: api.mon-projet.com
      caddy.encode: gzip
      caddy.reverse_proxy: "{{upstreams 8080}}"

      # Sécurité HTTP minimale
      caddy.header.Strict-Transport-Security: "max-age=31536000; includeSubDomains; preload"
      caddy.header.X-Content-Type-Options: "nosniff"
      caddy.header.X-Frame-Options: "DENY"

    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz || exit 1"]
      interval: 20s
      timeout: 3s
      retries: 3

networks:
  edge:
    external: true

📎 Fondamentaux :

  • Aucun ports: côté service (le frontal s'en charge).
  • Réseau edge identique à celui déclaré dans l'infra (Caddy frontal).
  • Label Watchtower si vous filtrez par --label-enable (cf. article labels).

5) DNS

Créer (ou mettre à jour) un enregistrement A/CNAME pour le domaine public du service :

api.mon-projet.com[IP du VPS]

Le Caddy frontal émettra automatiquement un certificat TLS (Let's Encrypt) au premier passage.


6) Premier déploiement

docker compose -f compose.prod.yaml up -d

Vérifications :

docker ps
docker logs -f infra-caddy-1         # Caddy frontal: découverte + certificat
curl -I https://api.mon-projet.com/healthz

Un HTTP/2 200 confirme la publication.


7) CI/CD (rappel rapide)

Si la chaîne CI → GHCR → Watchtower est déjà configurée, chaque push sur la branche principale régénère l'image :main et Watchtower redéploie le service automatiquement (aucune action manuelle).

Pour les détails et variants (tags, multi-arch, permissions), voir Chaîne complète : CI → GHCR privé → Watchtower → Prod.


8) Dépannage éclair

  • 404/502 via le frontal → vérifier caddy.reverse_proxy et le port interne (8080 vs 8000, etc.).
  • TLS/Let's Encrypt → attendre la propagation DNS ; contrôler les logs de infra-caddy-1.
  • Watchtower n'actualise pas → confirmer le label com.centurylinklabs.watchtower.enable: "true" et la présence des identifiants GHCR (voir Watchtower avec GHCR privé).
  • Symfony .env → inclure un .env non sensible dans l'image et surcharger via .env.prod (voir remarques dans Chaîne complète).

Références