Déploiement — BConnect POS¶
CI/CD par GitHub Actions, images stockées dans le registry Harbor de BINN,
déploiement par SSH + docker compose pull sur le VPS.
Le serveur n'héberge pas le code source : il ne fait que tirer une image déjà construite. La stack ne contient que l'application — MySQL, PostgreSQL, Redis et Traefik sont mutualisés par le projet infra du VPS et joints par réseau Docker externe.
| Workflow | .github/workflows/deploy.yml |
| Registry | binn.registry.169.58.138.150.nip.io (projet Harbor binn) |
| Image | binn.registry.169.58.138.150.nip.io/binn/bconnect-pos |
| Image docs | binn.registry.169.58.138.150.nip.io/binn/bconnect-pos-docs |
| Répertoire serveur | /srv/apps/BConnect-POS |
| Stack de prod | docker-compose.prod.yml (copié sur le serveur à chaque déploiement) |
| Réseaux externes | traefik-net, infra-data, infra-messaging, bconnect-net |
| URL publique | https://binn.pos.169.58.138.150.nip.io |
| URL docs | https://docs.bconnect-pos.169.58.138.150.nip.io |
La stack jumelle BConnect Business suit exactement le même modèle et partage le
réseau bconnect-net avec celle-ci.
1. Ce qui se passe automatiquement¶
Un push sur main ou develop, ou un tag v*, construit l'image applicative et
l'image docs, puis les pousse dans Harbor avec ces tags :
sha-<7 premiers caractères du commit>— le seul tag réellement immuable, c'est celui que le déploiement utilise ;- le nom de la branche (
main,develop) ; latest, uniquement sur la branche par défaut ;- le tag git, s'il s'agit d'un tag.
Le déploiement s'enchaîne automatiquement sur un push vers develop. Depuis
toute autre ref, il se lance à la main.
2. Déployer en production¶
Onglet Actions → workflow Build & Deploy → Run workflow.
- Use workflow from : la branche ou le tag à déployer.
- image_tag : laisser vide. L'image est reconstruite depuis la ref choisie puis
déployée. Renseigner un tag (ex.
sha-a1b2c3d) uniquement pour redéployer une image déjà publiée — typiquement un retour arrière : le build est alors sauté.
Le job de déploiement est rattaché à l'environnement GitHub production. Pour exiger
une approbation humaine avant qu'il ne s'exécute :
Settings → Environments → production → Required reviewers.
3. Configuration GitHub (à faire une fois)¶
Settings → Secrets and variables → Actions
Secrets¶
| Nom | Valeur |
|---|---|
REGISTRY_USERNAME |
compte Harbor (admin, ou de préférence un compte robot) |
REGISTRY_PASSWORD |
son mot de passe / jeton |
DEPLOY_HOST |
169.58.138.150 |
DEPLOY_USER |
abdoulaye |
DEPLOY_SSH_KEY |
clé privée SSH complète, sans passphrase (-----BEGIN … KEY----- inclus) |
DEPLOY_PATH |
/srv/apps/BConnect-POS |
Préférez un compte robot Harbor limité au projet
binnplutôt que le compteadmin: sa compromission ne donne alors accès qu'au push/pull d'images.
Variables (facultatif)¶
| Nom | Défaut | Rôle |
|---|---|---|
BCONNECT_DOCKER_NETWORK |
bconnect-net |
réseau Docker interne partagé avec la stack Business |
Cette application est rendue côté serveur : aucune valeur destinée au navigateur
n'est figée au build, tout vient du .env du serveur. Si une variable devait un
jour l'être, elle passerait par une variable GitHub et un build-arg, jamais par un
secret.
Environnement¶
Settings → Environments → New environment → production, puis y ajouter les
required reviewers si vous voulez une validation manuelle.
4. Préparation du serveur (à faire une fois)¶
Les points a, c, d, f et le contrôle des .env sont automatisés par
scripts/prepare-server.sh, à lancer sur le serveur en tant qu'utilisateur de
déploiement. Il est idempotent : le relancer ne fait que revérifier. Le point e
(bases MySQL) a son propre script, scripts/prepare-databases.sh. Les détails
ci-dessous restent la référence, et la marche à suivre si vous préférez tout faire à
la main.
bash scripts/prepare-server.sh
a. Docker et le registry¶
Si le certificat de Harbor n'est pas émis par une autorité reconnue — ce qui est le
cas par défaut avec un nom nip.io — le docker login échoue avec une erreur
x509: certificate signed by unknown authority. Déclarez le registry :
sudo tee /etc/docker/daemon.json >/dev/null <<'JSON'
{ "insecure-registries": ["binn.registry.169.58.138.150.nip.io"] }
JSON
sudo systemctl restart docker
Le workflow fait la même chose sur le runner GitHub. Le jour où Harbor a un certificat valide, ces deux configurations peuvent disparaître.
b. Clé SSH de déploiement¶
ssh-keygen -t ed25519 -C "github-actions-bconnect-pos" -f ~/.ssh/bconnect_pos_deploy -N ""
La partie publique va dans le ~/.ssh/authorized_keys de abdoulaye sur le serveur ;
la partie privée dans le secret DEPLOY_SSH_KEY.
c. sudo sans mot de passe¶
Le déploiement installe le fichier compose dans un répertoire appartenant à root et
pilote Docker : ces deux commandes doivent passer sans TTY ni mot de passe.
echo 'abdoulaye ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/install' \
| sudo tee /etc/sudoers.d/bconnect-deploy
sudo chmod 440 /etc/sudoers.d/bconnect-deploy
sudo visudo -c
Le même fichier sert aux deux stacks : inutile de le créer deux fois.
d. Répertoire de déploiement¶
sudo install -d -o root -g root -m 755 /srv/apps/BConnect-POS
Le workflow ne le crée pas : il vérifie sa présence avec test -d et s'arrête
avec un message explicite s'il manque. Un mkdir -p renverrait 0 sans rien faire et
masquerait un problème de droits.
e. Base de données MySQL¶
À faire avant le premier déploiement. La stack ne contient pas de conteneur
MySQL : la base et son utilisateur doivent exister sur l'instance mutualisée. L'image
ne les crée pas — son docker/entrypoint.sh attend que la connexion réponde, puis
joue doctrine:schema:update --force pour créer les tables.
CREATE DATABASE bconnect_pos CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'bconnect_pos'@'%' IDENTIFIED BY '<mot de passe>';
GRANT ALL PRIVILEGES ON bconnect_pos.* TO 'bconnect_pos'@'%';
FLUSH PRIVILEGES;
Sans cette étape, le conteneur affiche Attente de la base de donnees... puis
s'arrête au bout de deux minutes en affichant l'erreur SQL réelle — c'est le symptôme
à reconnaître.
f. Réseaux Docker¶
traefik-net, infra-data et infra-messaging sont créés et gérés par le projet
infra : ne les recréez pas. infra-messaging donne accès au service SMTP
mailer:1025. bconnect-net porte le trafic interne Business ↔ POS et est créé par
le workflow s'il manque.
docker network ls | grep -E 'traefik-net|infra-data|infra-messaging|bconnect-net'
g. Fichier .env de production¶
Les secrets ne transitent jamais par GitHub : ils vivent dans un .env que vous
créez à la main dans /srv/apps/BConnect-POS, à côté du docker-compose.prod.yml. Le
déploiement s'arrête avec une erreur explicite si ce fichier manque.
cd /srv/apps/BConnect-POS
sudo tee .env >/dev/null <<'ENV'
# --- MySQL mutualise (reseau infra-data) ---
# DB_HOST vaut `mysql` par defaut : inutile de le redefinir tant que le service
# mutualise porte ce nom sur infra-data.
DB_HOST=mysql
DB_PORT=3306
DB_NAME=bconnect_pos
DB_USER=bconnect_pos
DB_PASSWORD=<mot de passe>
DB_SERVER_VERSION=8.0
# --- Traefik ---
POS_HOST=binn.pos.169.58.138.150.nip.io
DOCS_HOST=docs.bconnect-pos.169.58.138.150.nip.io
TRAEFIK_ENTRYPOINT=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
# --- Symfony ---
APP_NAME="BConnect POS"
APP_SECRET=<openssl rand -hex 32>
CORS_ALLOW_ORIGIN=^https://binn\.(business|pos)\.169\.58\.138\.150\.nip\.io$
MAILER_DSN=smtp://mailer:1025
MAILER_FROM=noreply@binn.pro
JWT_PASSPHRASE=<openssl rand -hex 32>
LOCK_DSN=flock
# --- Liaison avec Business ---
# Appel serveur a serveur : passe par bconnect-net, sans sortir par Traefik.
BUSINESS_API_URL=http://bconnect-business
# Doit valoir exactement le SECRET_API_KEY du .env de la stack Business.
SYSTEM_API_KEY=<meme valeur que SECRET_API_KEY cote Business>
# --- Services externes ---
GEMINI_API_KEY=...
SMS_API_BASE_URL=https://mysms.brainsensetech.com/api/v1
SMS_SENDER_NAME=Bconnect
SMS_API_KEY=...
ENV
sudo chmod 600 .env
Toutes les variables marquées obligatoires dans docker-compose.prod.yml font échouer
le docker compose up avec un message nommant la variable manquante, plutôt que de
démarrer avec une valeur change-me.
Mot de passe MySQL : il est injecté dans une URL (
DATABASE_URL). S'il contient@,:,/,#ou%, encodez-le en pourcent (@→%40) ou changez-le pour une valeur alphanumérique.Secrets encore dans le dépôt :
compose.yamlcontient des clés OpenAI, Gemini et un mot de passe SMTP en clair, etconfig/jwt/*.pemest versionné avec sa passphrase dans.env. Rien de tout cela ne part en production (.dockerignoreles exclut, l'entrypoint génère de nouvelles clés), mais ces valeurs sont dans l'historique git et doivent être révoquées.
5. Traefik¶
Le conteneur ne publie aucun port : Traefik l'atteint par traefik-net. Les labels
sont dans docker-compose.prod.yml ; trois valeurs sont pilotees par le .env :
| Variable | Défaut | |
|---|---|---|
TRAEFIK_ENTRYPOINT |
websecure |
entrypoint Traefik |
TRAEFIK_CERTRESOLVER |
letsencrypt |
résolveur ACME |
DOCS_HOST |
docs.bconnect-pos.169.58.138.150.nip.io |
hote du portail docs |
Si votre Traefik ne termine pas le TLS (Cloudflare ou proxy amont devant), mettez
TRAEFIK_ENTRYPOINT=web et retirez les deux labels …tls… du fichier compose : une
variable ne suffit pas à supprimer un label.
Le label traefik.docker.network=traefik-net est indispensable ici — le conteneur est
sur trois réseaux, et sans lui Traefik peut router vers la mauvaise IP.
Le portail docs est servi par le service separe bconnect-pos-docs, avec nginx sur
le port interne 80. Son image est construite avec le meme tag que l'image
applicative et injectee au deploiement via DOCS_IMAGE.
6. Vérifier un déploiement¶
scripts/check-deploy.sh regroupe ces contrôles et y ajoute ce qui ne se voit pas
dans docker compose ps : que les deux stacks partagent bien bconnect-net et s'y
résolvent mutuellement, et que SECRET_API_KEY (Business) et SYSTEM_API_KEY (POS)
portent la même valeur.
bash scripts/check-deploy.sh
cd /srv/apps/BConnect-POS
docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs -f bconnect-pos
docker inspect --format '{{.Config.Image}}' bconnect-pos
La dernière commande affiche le tag exact en service — utile pour confirmer que le déploiement a bien pris.
7. Revenir en arrière¶
Relancez Run workflow en renseignant image_tag avec le sha-… de la version
précédente (visible dans Harbor, ou dans le résumé de l'exécution qui l'avait
déployée). Le build est sauté, seul le déploiement s'exécute.
8. Points d'attention¶
- Le schéma est mis à jour au démarrage par
doctrine:schema:update --force, pas par les migrations — c'est le comportement historique dedocker/docker.sh. Sur une base peuplée, cette commande peut proposer des changements destructeurs : à surveiller lors du premier déploiement, et à remplacer pardoctrine:migrations:migratequand la base de production sera établie. - Clés JWT : celles du dépôt sont exclues de l'image. L'entrypoint en génère de
nouvelles au premier démarrage dans le volume
pos_jwtavecJWT_PASSPHRASE. Changer cette passphrase après coup rend les clés existantes illisibles : il faut alors supprimer le volume, ce qui révoque tous les jetons en circulation. - Volume
pos_uploads: photos produits, profils et documents. Il survit aux déploiements et n'est jamais recréé par le workflow ; sa sauvegarde reste à votre charge. La base MySQL, elle, est sauvegardée avec l'instance mutualisée. LOCK_DSN=flockest local au conteneur : si la stack est un jour répliquée, basculez sur le Redis mutualisé (redis://redis:6379), sinon les verrous ne sont plus partagés entre les instances.docker image prune -fest exécuté après chaque déploiement pour éviter que le disque ne se remplisse d'images obsolètes. Il ne touche qu'aux images sans conteneur associé.