Déploiement

VPS Debian 13 · Docker · nginx

Déploiement containerisé sur un VPS dédié, déclenché par push sur la branche deploy-elynav-command.

Infrastructure runtime

graph TB subgraph Internet["Internet"] Browser[Navigateur
du CTO] Brevo[Brevo
webhooks email] end subgraph VPS["VPS Debian 13 — 82.165.222.108"] subgraph Edge["Edge — TLS + reverse proxy"] Nginx[nginx
:80 → :443 → :8080] Certbot[certbot timer
renouvellement auto] end subgraph Docker["Docker Engine"] Container[elynav-command
:8080 loopback only
ASP.NET Core + claude CLI] end subgraph Volumes["/opt/elynav-command/data/ — bind mounts"] CRM["/crm/
elynav.db + WAL"] Content["/content/
drafts agents"] Reports["/reports/
historiques"] ClaudeHome["/claude-home/
session Claude"] end Plesk[Apache + Plesk
désactivé pour libérer :80/:443] end Browser -->|HTTPS
elynav-command.tikoad-services.info| Nginx Brevo -->|POST /api/brevo/events| Nginx Nginx -->|loopback| Container Certbot -.->|certs| Nginx Container --> CRM Container --> Content Container --> Reports Container --> ClaudeHome

Pipeline CI/CD

sequenceDiagram autonumber actor Dev as Estéban / Yann participant GH as GitHub participant Actions as GitHub Actions participant VPS as VPS participant Docker as docker compose participant App as elynav-command (conteneur) Dev->>GH: git push origin deploy-elynav-command GH->>Actions: trigger workflow Deploy ElynavCommand Actions->>Actions: vérifier env elynav-command-production
(secrets : VPS_HOST, VPS_SSH_KEY, BREVO_*) Actions->>VPS: ssh appleboy/ssh-action@v1 VPS->>VPS: git pull --hard origin deploy-elynav-command VPS->>VPS: write .env (BREVO_*) VPS->>Docker: docker compose build --pull Docker->>Docker: stage 1 — dotnet publish
stage 2 — install Node + claude VPS->>Docker: docker compose up -d Docker->>App: start loop 30 tentatives x 5s Docker->>App: healthcheck curl http://localhost:8080 App-->>Docker: 200 OK end Actions-->>Dev: job vert / rouge avec logs

Image Docker — couches

graph TB subgraph Build["Stage build — mcr.microsoft.com/dotnet/sdk:10.0"] B1[COPY csproj] B2[dotnet restore] B3[COPY src/] B4[dotnet publish -c Release] end subgraph Runtime["Stage runtime — mcr.microsoft.com/dotnet/aspnet:10.0"] R1[apt: curl, ca-certificates, gnupg, sqlite3] R2[NodeSource setup_20.x + apt: nodejs] R3[npm install -g @anthropic-ai/claude-code] R4[COPY --from=build /app/publish .] R5[ENV ASPNETCORE_URLS=http://+:8080] R6[ENTRYPOINT dotnet ElynavCommand.Web.dll] end B4 -.publish output.-> R4

L'image runtime fait ~600 Mo après installation de Node + le CLI Claude. Le build est cacheable couche par couche, donc les rebuilds incrémentaux après modif de code sont rapides (~1 min).

Workflow GitHub Actions — extraits clés

name: Deploy ElynavCommand
on:
  push:
    branches: [deploy-elynav-command]
  workflow_dispatch:

concurrency:
  group: deploy-elynav-command
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    environment: elynav-command-production    # required pour accéder aux secrets

    steps:
      - uses: appleboy/ssh-action@v1
        with:
          host:     ${{ secrets.ELYNAV_VPS_HOST }}
          username: ${{ secrets.ELYNAV_VPS_USER }}
          key:      ${{ secrets.ELYNAV_VPS_SSH_KEY }}
          script: |
            cd /opt/elynav-command/repo
            git fetch && git reset --hard origin/deploy-elynav-command
            cd ElynavCommand/deploy
            docker compose build --pull
            docker compose up -d
            # healthcheck loop ...

Secrets GitHub (environnement elynav-command-production)

Secret Usage
ELYNAV_VPS_HOSTIP du VPS pour la connexion SSH
ELYNAV_VPS_USERUser SSH (esteban, membre des groupes sudo + docker)
ELYNAV_VPS_PORTPort SSH (22)
ELYNAV_VPS_SSH_KEYClé privée ed25519 dédiée au workflow (jamais réutiliser une clé perso)
BREVO_API_KEYClé API Brevo pour envoi d'emails transactionnels
BREVO_WEBHOOK_TOKENToken partagé pour authentifier les webhooks Brevo (query param ?token=)

Commandes utiles sur le VPS

# Statut du conteneur
cd /opt/elynav-command/repo/ElynavCommand/deploy
docker compose ps
docker inspect -f '{{.State.Health.Status}}' elynav-command

# Logs live
docker compose logs -f elynav-command

# Shell dans le conteneur
docker exec -it elynav-command bash

# Inspecter la DB (depuis l'hôte, sqlite3 installé)
sqlite3 /opt/elynav-command/data/crm/elynav.db '.tables'

# Backup rapide
cp /opt/elynav-command/data/crm/elynav.db ~/backup-$(date +%F).db
tar czf ~/elynav-backup-$(date +%F).tgz /opt/elynav-command/data

# Rollback
cd /opt/elynav-command/repo
git log --oneline -5            # repérer le commit précédent OK
git reset --hard <sha>
cd ElynavCommand/deploy && docker compose up -d --build

Points de vigilance

  • SQLite = 1 seule instance
    Ne jamais scaler à plus de 1 réplica. database is locked garanti sinon.
  • Cohabitation Plesk
    Le VPS a Plesk installé. Apache+nginx-Plesk doivent rester désactivés (systemctl disable apache2) pour libérer 80/443. Ne pas créer de site web Plesk pour le domaine, ça écraserait le vhost nginx custom.
  • Session Claude
    Si elle expire un jour, refaire le login OAuth depuis le VPS (en esteban), puis recopier ~/.claude.json + ~/.claude/ dans /opt/elynav-command/data/claude-home/, chmod 600 .claude.json, puis docker compose restart.
  • Mode bypassPermissions bloqué en root
    Le conteneur tourne en root par défaut. Le CLI Claude refuse bypassPermissions dans ce contexte. En prod, l'agent dev-executor doit donc utiliser le mode default (chat-only). Le pair-programming reste un usage local.