Statut : stable Public cible : équipe ops, contributeur Dernière vérification : 2026-04-27 Sources de vérité :
config/deploy.staging.yml,config/deploy.production.yml,.github/workflows/deploy-staging.yml,.github/workflows/deploy-production.yml,.github/workflows/deploy-promote-main.yml,Dockerfile.
Source de vérité unique pour le déploiement Kamal (staging + production). Fusion de l'ancien
knowledge/DEPLOYMENT_GUIDE.mdet des « règles d'or » deknowledge/KNOWLEDGE_BASE.md. Pour un déploiement bare-metal alternatif (sans Kamal), voir../internal/sqlite_deployment.md.Branches actives (cf.
.github/workflows/deploy-*.yml) :dev(intégration) →staging(déploie staging) →main(déploie production).
Ce sont deux étapes distinctes :
- Git : la branche
stagingpointe sur un commit (merge depuisdev, promote, ou push manuel). Les « fichiers » sont à jour sur GitHub. - CI/CD : le workflow
deploy-stagingconstruit l’image Docker, la pousse vers GHCR, puis Kamal déploie sur le VPS. Si cette étape échoue ou ne tourne pas, le serveur garde l’ancienne image.
Cas fréquents :
| Situation | Effet |
|---|---|
Push fait par GitHub Actions avec GITHUB_TOKEN (promote) |
Le push ne déclenche pas un second workflow on: push. Il faut le workflow_dispatch explicite (gh workflow run deploy-staging) — c’est ce que fait deploy-promote-to-staging, qui attend maintenant la fin du déploiement (succès ou échec visible dans le même job promote). |
Variables vars.STAGING_SERVER_IP ou secrets manquants |
Le job deploy ou build échoue ; le merge Git peut quand même être vert. |
| Erreur Kamal / proxy / registry | Même constat : corriger les logs du workflow deploy-staging. |
git checkout dev
# … modifications …
git checkout staging
git merge dev
git push origin staging
# → .github/workflows/deploy-staging.yml se déclenche
git checkout main
git merge staging
git push origin main
# → .github/workflows/deploy-production.yml se déclencheRègle d'or — Aucune modification directe sur staging ni main.
Toujours créer une branche dédiée :
git checkout -b fix/nom-du-probleme
# … travail …
git push origin fix/nom-du-probleme
# Merger ensuite vers dev → staging → main# Staging
curl -I https://staging.lecircographe.fr # HTTP 200
curl -I https://staging.lecircographe.fr/up # HTTP 200 (sans auth)
# Production
curl -I https://lecircographe.fr # HTTP 200
curl -I https://lecircographe.fr/up # HTTP 200RAILS_MASTER_KEY— clé de chiffrement RailsSECRET_KEY_BASE— clé secrète RailsSTAGING_PASSWORD— mot de passe HTTP Basic stagingSSH_PRIVATE_KEY— clé SSH déploiement VPS
STAGING_SERVER_IPPRODUCTION_SERVER_IP
Ces règles sont issues de plusieurs incidents en staging/production. Les ignorer casse les health checks Kamal ou rend le conteneur inaccessible.
return @app.call(env) if request.path == "/up"Pourquoi : Kamal teste /up pour décider de la santé du conteneur.
Toute réponse 401/301 sur /up ⇒ conteneur considéré comme down ⇒ rollback.
- Assets précompilés requis en staging/prod.
- Une
SECRET_KEY_BASEdummy suffit pendant le build. public/assetsdoit être inclus dans l'image Docker (donc absent de.dockerignore).
RUN SECRET_KEY_BASE="a1b2c3...dummy...e1f2" RAILS_ENV=staging \
./bin/rails assets:precompile- Build time : pas de
ENV RAILS_ENVen dur dans leDockerfile. - Runtime : configuré via Kamal (staging ou production).
- Conséquence : une seule image Docker pour 2 environnements applicatifs.
# config/environments/{staging,production}.rb
config.hosts.clear # autoriser les container IDs Docker (ex: f4753d38178e)Pourquoi : kamal-proxy utilise les IDs de conteneur comme hostnames lors
des healthchecks. Sans hosts.clear, Rails renvoie Blocked hosts.
config.ssl_options = {
redirect: { exclude: ->(request) { request.path == "/up" } }
}Pourquoi : kamal-proxy frappe /up en HTTP. Un redirect 301 → https
fait échouer le healthcheck.
| Erreur | Cause probable | Solution |
|---|---|---|
Propshaft::MissingAssetError |
Assets non compilés | Vérifier Dockerfile + .dockerignore (cf. § 6.1) |
HTTP 401 sur /up |
Middleware d'auth bloque healthcheck | Exclure /up du middleware (§ 4.1) |
Blocked hosts: f4753…:80 |
Host Authorization Rails | config.hosts.clear (§ 4.4) |
Healthcheck timeout (301) |
force_ssl redirige /up |
ssl_options exclude /up (§ 4.5) |
Exit 255 |
Conteneur unhealthy | Vérifier logs + healthchecks |
Missing secret_key_base |
Credentials manquantes | Vérifier GitHub Secrets |
SharedEnvironmentConfig Error |
Module inexistant inclus | Supprimer include SharedEnvironmentConfig |
config.public_file_server.enabled = trueconfig.assets.paths << Rails.root.join("app", "assets", "builds")- Précompilation pendant le build avec dummy
SECRET_KEY_BASE.
Cas particulier (résolu 2025-10-12) : application.scss ne génère pas
application.css avec Propshaft + DartSass. Solution : renommer en
application.css avec contenu CSS complet.
Tailwind CSS → app/assets/tailwind/application.css → app/assets/builds/
DartSass → app/assets/stylesheets/application.scss → ❌ ne génère pas .css
Propshaft → cherche "application" → veut "application.css"
Conclusion : le fichier final doit s'appeler application.css pour Propshaft.
@font-face {
font-family: 'Circographe';
src: font-url('Circographe-Regular.woff2') format('woff2');
font-display: swap;
}font-url()→ résolu par Rails vers/assets/après compilation.- Formats multiples (
woff2,woff,otf) pour compatibilité. font-display: swappour éviter le flash de texte invisible.
config/deploy.staging.yml— cléimage:lecircographe-asso/circographe-staging(sansghcr.io/) ; le registry dans le YAML complète l’URL.- Hôtes :
STAGING_SERVER_IP/PRODUCTION_SERVER_IPsont lus viaENV.fetch: en local, exporter l’IP avantkamal(sinon erreur hosts/0). Sur GitHub Actions :vars.*. kamal proxy rebooten non-interactif (CI) :bundle exec kamal proxy reboot --confirmed -c config/deploy.staging.yml(évite la question Are you sure?).
- Build : assets compilés avec dummy key.
- Runtime :
RAILS_ENVinjecté via Kamal. - Middlewares activés conditionnellement selon variables d'env.
volumes:
- "circographe_staging_storage:/rails/storage" # base SQLite
- "/srv/www/lecircographe_staging/log:/app/log" # logs accessiblesSans volumes, la base SQLite serait recréée à chaque déploiement.
# staging.rb / production.rb
config.secret_key_base = ENV["SECRET_KEY_BASE"] ||
Rails.application.credentials.secret_key_base- Build : dummy
SECRET_KEY_BASEpour précompilation. - Runtime :
RAILS_MASTER_KEYdéchiffrecredentials.yml.enc. - Kamal exporte
RAILS_MASTER_KEY+SECRET_KEY_BASEvia.kamal/secrets. - Ordre de priorité :
ENV→credentials.yml.enc.
# config/deploy.staging.yml
proxy:
ssl: true # Let's Encrypt automatique
host: staging.lecircographe.fr
app_port: 80 # port interne conteneur
volumes:
- "circographe_staging_storage:/rails/storage"
- "/srv/www/lecircographe_staging/log:/app/log"Objectif : maintenance activée par défaut, admin garde l'accès, healthcheck /up toujours OK.
/up→ OK (healthcheck Kamal)./sessions/new→ OK (login)./admin/*→ OK si admin connecté.- Autres →
503(page maintenance).
MAINTENANCE_MODE=true
RAILS_ENV=production
SECRET_KEY_BASE=<secret>
RAILS_MASTER_KEY=<master_key>app/middleware/maintenance_mode_middleware.rb
- Autoriser
/up. - Autoriser
/sessions/newet/sessions. - Autoriser
/admin/*si session admin valide. - Sinon : page maintenance (
503).
Utiliser Rails.cache.fetch("opening_hours") avec fallback si cache indisponible.
- Depuis avril 2025,
assets:precompiledéclenche automatiquementimportmap:normalize_modules. - Le hook copie chaque module ES fingerprinté
(
public/assets/_/Cfv2l5G4-*.js) vers un alias sans digest (public/assets/_/Cfv2l5G4.js). - En local,
bin/rails_assets_resetexécute la même opération (étape 8).
- Kamal / Docker build : rien à ajouter, le hook tourne durant
assets:precompile. - VPS / serveurs : après
kamal deploy, vérifierls public/assets/_/. - Fallback :
bin/rails importmap:normalize_modules.
docker logs <container-id> --tail 50
gh run list --limit 5
gh run view <run-id> --log
gh run watch
kamal rollback -c config/deploy.staging.yml
docker exec -it <container-id> bash- Mélanger les environnements (build en
development, runtime enproduction). - Inclure des credentials réelles dans le
Dockerfile(la dummy key suffit pour le build). - Oublier d'exclure
/upd'un middleware (§ 4.1). - Exclure
public/assetsdu.dockerignore(doit y rester). - Coder
RAILS_ENVen dur dans leDockerfile. - Oublier
config.hosts.clear(§ 4.4). - Forcer SSL sur
/up(§ 4.5). - Ne pas exporter
RAILS_MASTER_KEYcôté Kamal. - Coder les credentials sans fallback
ENV.
Voir aussi : ../internal/optimizations_backlog.md (backlog optimisation infra/Docker) · ../internal/sqlite_deployment.md (alternative bare-metal sans Kamal).