---
title: "FrankenDeploy : votre application Symfony en production sur un VPS, en cinq commandes"
excerpt: "Docker, pare-feu, certificat HTTPS, mise en ligne sans coupure : l'essentiel de ce qui sépare « ça marche en local » de « c'est en ligne », FrankenDeploy s'en charge. Démonstration avec la Symfony Demo, d'un VPS nu jusqu'au HTTPS."
publishDate: 2026-09-26T00:00:00.000Z
tags: ["symfony", "php", "frankenphp", "frankendeploy", "deploiement", "vps", "docker", "caddy", "lets-encrypt", "devops", "open-source", "tutorial", "production"]
canonical: "https://yoandev.co/frankendeploy-deployer-symfony-vps"
---

Vous avez une application Symfony qui tourne sur votre machine. Vous aimeriez la mettre en ligne : sur un petit serveur à quelques euros par mois, avec une vraie adresse et le cadenas HTTPS dans la barre du navigateur.

Et c'est à ce moment que tout se complique. Docker, pare-feu, certificats, noms de domaine : la mise en production demande un tout autre métier que le développement. Beaucoup de projets personnels s'arrêtent à cette étape, et beaucoup de débutants y restent coincés.

J'ai créé **FrankenDeploy** pour que cette étape ne soit plus un obstacle. Cet article se lit en deux temps :

1. **La présentation** : ce qui rend la mise en production si difficile, et comment FrankenDeploy s'en charge à votre place ;
2. **Le tutoriel** : on déploie ensemble la Symfony Demo, sur un serveur tout neuf, de la première commande jusqu'au site en HTTPS. Vous pourrez refaire chaque étape chez vous.

---

## Partie 1 : la mise en production, et ce que FrankenDeploy change

### Trois murs entre votre machine et un site en ligne

Entre votre machine et un site accessible à tous, on retrouve toujours les mêmes obstacles :

- **Empaqueter l'application** : écrire un Dockerfile de production, c'est-à-dire la recette qui transforme votre code en une « boîte » prête à tourner n'importe où. Avec la bonne version de PHP, les bonnes extensions, les assets compilés, et en suivant les bonnes pratiques de sécurité.
- **Protéger le serveur** : un serveur fraîchement loué est attaqué par des robots en quelques heures. Il lui faut un pare-feu, une protection contre les tentatives de connexion à répétition, et Docker pour faire tourner votre « boîte ».
- **Le rendre accessible** : un nom de domaine, un certificat HTTPS (qu'il faut renouveler), et des mises à jour qui ne coupent pas le site à chaque nouvelle version.

Les outils existent, et ils sont bons : [symfony-docker](https://github.com/dunglas/symfony-docker) fournit un excellent point de départ, [Kamal](/deployer-en-prod-votre-app-symfony-avec-kamal) sait déployer. Mais ils partent du principe que vous maîtrisez déjà Docker, l'administration d'un serveur, les noms de domaine et les certificats. Quand on débute, on se retrouve avec un très bon Dockerfile… et la question « et maintenant ? » tout entière.

C'est ce fossé que FrankenDeploy essaie de combler.

### FrankenDeploy en deux minutes

[FrankenDeploy](https://github.com/yoanbernabeu/frankendeploy) est un outil en ligne de commande, open source (licence MIT), qui déploie une application Symfony sur n'importe quel VPS (un serveur loué au mois) sous Ubuntu ou Debian. Il s'appuie sur [FrankenPHP](https://frankenphp.dev/), un serveur d'applications PHP moderne et rapide.

Son fonctionnement repose sur trois principes :

- **Il lit votre projet.** En parcourant `composer.json` et la configuration Symfony, il trouve la version de PHP, les extensions, la base de données, la façon dont les assets sont construits. Il écrit le Dockerfile à votre place.
- **Il prépare le serveur.** Il installe Docker, un pare-feu, une protection contre les attaques par force brute, et [Caddy](https://caddyserver.com/), le serveur web qui reçoit les visiteurs et obtient lui-même le certificat HTTPS. Le tout avec des commandes Linux classiques : rien de caché, rien de propriétaire sur votre serveur.
- **Il met à jour sans coupure.** La nouvelle version démarre à côté de l'ancienne. FrankenDeploy vérifie qu'elle répond, et seulement ensuite lui envoie les visiteurs. Si elle ne répond pas, l'ancienne version reste en place et le site continue de fonctionner.

Vous pouvez héberger plusieurs applications sur le même serveur, chacune avec son nom de domaine. Chacune a son propre réseau : une application compromise ne peut pas joindre directement les autres.

Retenez aussi ces fichiers, et leur rôle :

| Fichier | Où | Ce qu'il décrit |
|---|---|---|
| `frankendeploy.yaml` | Dans le projet, commité avec le code | **L'application** : PHP, base, assets, domaine |
| `config.yaml` | Sur votre machine (`~/Library/Application Support/frankendeploy/` sur macOS, `~/.config/frankendeploy/` sur Linux), jamais commité | **Vos serveurs** : adresse, utilisateur, clé SSH, préférences |
| `.env.local` | Sur le serveur, lisible par vous seul | **Les secrets de production** (mots de passe, clés) : ils ne passent jamais par Git. `frankendeploy env list prod` en liste le contenu, valeurs masquées |

Autrement dit : vos réglages restent chez vous, vos secrets restent sur le serveur.

---

## Partie 2 : déployer la Symfony Demo sur un VPS

Place à la pratique. On va déployer la [Symfony Demo](https://github.com/symfony/demo), l'application d'exemple officielle de Symfony : un petit blog avec ses articles, ses commentaires et un espace d'administration. Elle a l'avantage d'être connue et d'avoir une vraie base de données.

**À la fin, vous aurez** la Symfony Demo accessible à une adresse en `https://`, sur votre propre serveur sécurisé, et vous saurez la redéployer en une commande à chaque modification.

**La feuille de route :**

| Étape | Commande | Ce qu'elle fait | Durée |
|---|---|---|---|
| 0 | — | Louer le serveur, faire pointer le nom de domaine, installer FrankenDeploy | variable (le nom de domaine peut mettre quelques heures à pointer) |
| 1 | `frankendeploy init` | Analyser le projet et écrire sa configuration | instantané |
| 2 | `frankendeploy server add`, puis `doctor` | Déclarer le serveur, puis faire le bilan | quelques secondes |
| 3 | `frankendeploy server setup`, puis `doctor` | Installer et sécuriser le serveur, puis vérifier | quelques minutes |
| 4 | `frankendeploy deploy` | Déployer | quelques minutes la première fois, plus rapide ensuite |

C'est parti.

### Étape 0 : ce qu'il vous faut

| | Quoi | Remarque |
|---|---|---|
| 1 | Un **VPS** Ubuntu 24.04 ou Debian 12 | Chez n'importe quel hébergeur. Le plus petit modèle suffit (ici 1 processeur et 2 Go de mémoire) |
| 2 | Un accès **SSH par clé** | L'utilisateur fourni par l'hébergeur (`root`, `ubuntu` ou `debian`) fait l'affaire. S'il n'est pas `root`, il doit pouvoir utiliser `sudo` sans mot de passe (c'est le cas chez la plupart des hébergeurs) |
| 3 | Un **nom de domaine** | Chez votre registrar, un enregistrement DNS de type **A** qui pointe vers l'adresse IP du VPS |
| 4 | **FrankenDeploy** sur votre machine | macOS, Linux ou Windows |
| 5 | **Git** | Pour cloner la démo, et FrankenDeploy s'en sert pour savoir quels fichiers envoyer |

**Pas besoin de PHP, de Composer ni de Docker sur votre machine** : tout sera installé et construit sur le serveur. Pour Docker, un réglage suffit si vous n'êtes pas sur un Mac à puce Apple, on le verra à l'étape 2.

Dans la suite, le VPS a l'adresse `203.0.113.42`, une adresse fictive réservée à la documentation, et le domaine est `demo.yoandev.co`. Remplacez-les par les vôtres.

#### Vérifier l'accès SSH et le DNS

Avant tout, connectez-vous une première fois à la main. SSH vous demande de confirmer l'identité du serveur : répondez `yes`, puis tapez `exit` pour revenir.

```bash
ssh ubuntu@203.0.113.42
```

Puis vérifiez que votre nom de domaine pointe bien vers le serveur. La commande doit afficher l'adresse IP du VPS :

```bash
dig +short demo.yoandev.co
# 203.0.113.42
```

Sans cela, impossible d'obtenir le certificat HTTPS. La modification peut mettre de quelques minutes à quelques heures à se propager, et FrankenDeploy le vérifiera de toute façon.

#### Installer FrankenDeploy

Avec Homebrew (macOS, Linux) :

```bash
brew install yoanbernabeu/tap/frankendeploy
frankendeploy --version
```

> Si Homebrew refuse avec le message « Refusing to load formula … from untrusted tap », c'est une sécurité des versions récentes pour les sources qui ne viennent pas de Homebrew lui-même. Autorisez celle-ci avec `brew trust yoanbernabeu/tap`, puis relancez l'installation.

Sans Homebrew (sous Windows par exemple), les autres méthodes sont décrites dans la [page d'installation](https://yoanbernabeu.github.io/frankendeploy/installation/).

### Étape 1 : `init`, la détection

On récupère la Symfony Demo. Rien d'autre à installer, pas même les dépendances avec `composer install` :

```bash
git clone https://github.com/symfony/demo.git
cd demo
```

Puis on laisse FrankenDeploy lire le projet :

```
$ frankendeploy init --domain demo.yoandev.co
ℹ️  Analyzing project...
✅ Created frankendeploy.yaml

📋 Project Configuration:
   Name:        demo
   PHP:         8.4
   Extensions:  ctype, iconv, pdo_sqlite, intl, opcache, zip
   Database:    sqlite 3
   Assets:      assetmapper
   Domain:      demo.yoandev.co

Next steps:
  1. Review frankendeploy.yaml and adjust if needed
  2. Run 'frankendeploy build' to generate Docker files
  3. Run 'frankendeploy dev up' to start local development
```

Ignorez les deux dernières suggestions : `deploy` générera lui-même les fichiers Docker (on le verra), et `dev up` sert à développer en local avec Docker, ce n'est pas notre sujet.

FrankenDeploy a trouvé PHP 8.4, les extensions PHP nécessaires, **SQLite** comme base de données (un simple fichier), et **AssetMapper** pour le CSS et le JavaScript. Voici le fichier qu'il a écrit :

```yaml
name: demo
php:
    version: "8.4"
    extensions:
        - ctype
        - iconv
        - pdo_sqlite
        - intl
        - opcache
        - zip
database:
    driver: sqlite
    version: "3"
    path: data/database.sqlite
assets:
    build_tool: assetmapper
    output_dir: public/assets
mailer:
    enabled: true
deploy:
    domain: demo.yoandev.co
    healthcheck_path: /
    keep_releases: 5
    shared_files:
        - .env.local
    shared_dirs:
        - var/log
        - var/sessions
        - data
```

Le point important est en bas : **`data` est un dossier partagé**. La Symfony Demo range sa base de données dans `data/database.sqlite`. Un dossier partagé est conservé sur le serveur d'une version à l'autre : sans lui, chaque déploiement effacerait vos données.

Sur un projet qui utilise les migrations Doctrine (les fichiers qui font évoluer la structure de la base), FrankenDeploy ajouterait aussi leur exécution automatique à chaque déploiement, avec une sauvegarde de la base juste avant. La Symfony Demo n'en a pas, il n'ajoute donc rien.

Ce fichier est à vous : relisez-le, modifiez-le si besoin, et commitez-le avec le reste du projet.

### Étape 2 : déclarer le serveur, et faire le bilan

On déclare le serveur sous le nom `prod` :

```
$ frankendeploy server add prod ubuntu@203.0.113.42
✅ Added server 'prod' (ubuntu@203.0.113.42)
ℹ️  Testing SSH connection...
✅ SSH connection successful

Next step:
  Run 'frankendeploy server setup prod --email your@email.com' to configure the server
```

Cette fiche est enregistrée sur votre machine, dans `config.yaml`, pas dans le projet.

**Pas de Docker sur votre machine, et pas de Mac à puce Apple ?** Demandez dès maintenant à FrankenDeploy de construire l'application sur le serveur, sinon il essaiera de le faire sur votre machine :

```bash
frankendeploy server set prod remote_build true
```

Sur un Mac à puce Apple, inutile : FrankenDeploy vous le proposera tout seul à l'étape 4.

Avant d'installer quoi que ce soit, faisons le bilan avec `doctor`. Il vérifie tout ce dont un déploiement a besoin et, pour chaque problème, dit comment le corriger :

```
$ frankendeploy doctor prod

✅ Local Docker — daemon 29.4.0
✅ SSH connection — ubuntu@203.0.113.42:22
✅ Remote sudo
❌ Remote Docker — docker not found on the server
      Run 'frankendeploy server setup <name> --email you@example.com' to install Docker and Caddy.
❌ Docker network — network "frankendeploy" missing
      Run 'frankendeploy server setup <name> --email you@example.com', or create it manually:
        docker network create frankendeploy
❌ Caddy proxy — caddy container not running
      Run 'frankendeploy server setup <name> --email you@example.com' to (re)start the reverse proxy.
✅ Disk space — 16.3 GB free (11% used)
✅ DNS demo.yoandev.co — → 203.0.113.42

Error: doctor found blocking problems
```

C'est normal : le serveur est vierge. La connexion, les droits et le nom de domaine sont bons, il manque Docker et Caddy. Et `doctor` nous dit quoi faire : `server setup`. Si Docker n'est pas installé sur votre machine, la première ligne s'affiche en simple avertissement, ce n'est pas bloquant.

### Étape 3 : préparer le serveur

Suivons le conseil. L'adresse e-mail sert à ouvrir votre compte chez Let's Encrypt, l'organisme gratuit qui délivre les certificats HTTPS :

```
$ frankendeploy server setup prod --email vous@example.com
✅ Connected to 203.0.113.42
ℹ️  Setting up server for FrankenDeploy...
ℹ️  [1/5] Installing prerequisites...
ℹ️  [2/5] Installing Fail2ban...
ℹ️  [3/5] Installing Docker...
ℹ️  Server network MTU is 1400: configuring Docker to match
ℹ️  [4/5] Configuring FrankenDeploy...
ℹ️  [5/5] Configuring firewall and starting Caddy...
✅ Caddy container is running
✅ Server 'prod' is ready for deployments!

Configuration:
  Email:    vous@example.com (for Let's Encrypt)
  Caddy:    Docker container with Admin API
  Docker:   Installed with 'frankendeploy' network
  Firewall: Ports 22, 80, 443 open
  Fail2ban: SSH protection enabled (5 retries, 1h ban)

Next step:
  Run 'frankendeploy deploy prod' from your Symfony project
```

Quelques instants plus tard, tout ce qu'on ne pense pas à faire quand on débute est fait :

- **Fail2ban** bloque pendant une heure toute adresse qui rate 5 fois sa connexion SSH : les robots qui essaient des mots de passe au hasard sont écartés ;
- **le pare-feu** (UFW) ferme toutes les portes du serveur, sauf trois : SSH pour vous, HTTP et HTTPS pour vos visiteurs. Et il ouvre la porte SSH **avant** de s'activer, pour ne pas vous enfermer dehors ;
- **Docker** est installé et prêt à l'emploi ;
- **Caddy** attend vos visiteurs et s'occupera des certificats HTTPS.

Une ligne peut surprendre : `Server network MTU is 1400`. Pas d'inquiétude : c'est un réglage réseau propre à certains hébergeurs. Sans lui, certains téléchargements resteraient bloqués pendant le déploiement. FrankenDeploy s'en aperçoit et règle Docker de lui-même, vous n'avez rien à faire. Chez la plupart des hébergeurs, cette ligne n'apparaît même pas.

On relance le bilan :

```
$ frankendeploy doctor prod

✅ Local Docker — daemon 29.4.0
✅ SSH connection — ubuntu@203.0.113.42:22
✅ Remote sudo
✅ Remote Docker — 29.8.1
✅ Docker network — frankendeploy
✅ Caddy proxy — Up 2 seconds
✅ Disk space — 15.9 GB free (14% used)
✅ Network MTU — 1400, Docker aligned
✅ App network — frankendeploy-demo (created by the first deploy)
✅ DNS demo.yoandev.co — → 203.0.113.42

✅ Everything looks good — ready to deploy!
```

Tout est vert. Le réflexe à garder : **au moindre doute, `doctor`**. La ligne DNS, en particulier, évite le piège le plus courant : un nom de domaine qui ne pointe pas encore vers le serveur, et donc pas de certificat HTTPS.

### Étape 4 : `deploy`

Le moment de vérité :

```bash
frankendeploy deploy prod
```

FrankenDeploy peut poser jusqu'à deux questions, la première seulement sur un Mac à puce Apple. Voici la sortie complète, réponses comprises :

```
ℹ️  Deploying demo to prod...
✅ Connected to 203.0.113.42
⚠️  Architecture mismatch detected:
   Local:  arm64 (Apple Silicon)
   Server: x86_64

   Local builds will not run on this server.

Use remote build for this server?
  [1] Use remote build for this server (Recommended)
  [2] Continue with local build anyway
  [0] Skip

? Select: 1
✅ Server 'prod' configured for remote builds
ℹ️  Running pre-flight checks...
⚠️  Missing required environment variables:
   - APP_SECRET

How would you like to proceed?
  [1] Generate missing secrets automatically (Recommended)
  [2] Show commands to set manually
  [0] Skip

? Select: 1
✅ Generated APP_SECRET
ℹ️  Continuing deployment...
✅ Pre-flight checks passed
ℹ️  Docker artifacts missing — generating them (equivalent to 'frankendeploy build')
✅ Generated Dockerfile
✅ Generated docker-entrypoint.sh
✅ Generated .dockerignore
ℹ️  Transferring source code to server...
ℹ️    247 files transferred
✅ Source code transferred
ℹ️  Building Docker image on server...
✅ Image built: demo:20260926-180127
ℹ️  Preparing application network...
✅ Created network frankendeploy-demo
ℹ️  Preparing release...
ℹ️  Initialized shared data/ with 2 file(s) committed to Git
ℹ️  Starting new version (blue-green)...
ℹ️  Running health check...
✅ Health check passed
ℹ️  Swapping containers...
ℹ️  Updating reverse proxy...
✅ Caddy configured for demo.yoandev.co
ℹ️  Cleaning up old releases...
✅ Deployment complete in 4m31s!

Application deployed: demo
  Tag: 20260926-180127
  URL: https://demo.yoandev.co
```

Lisons-la dans l'ordre.

**Les deux questions.**

- **Le type de processeur.** Mon Mac (puce Apple) et le serveur (processeur Intel/AMD) ne parlent pas le même langage machine : une application préparée sur le Mac ne démarrerait pas sur le serveur, avec un message d'erreur incompréhensible. FrankenDeploy le repère avant, et propose de tout préparer **directement sur le serveur**. La réponse est enregistrée, la question ne reviendra plus. C'est l'équivalent automatique de la commande `server set` vue à l'étape 2.
- **`APP_SECRET`.** C'est une clé secrète dont Symfony a besoin en production, et qui ne doit jamais être dans votre dépôt Git. FrankenDeploy la génère et la range **sur le serveur**, dans le `.env.local` lisible par vous seul.

Pour ajouter un autre secret plus tard, une clé d'API par exemple, c'est une commande. Grâce à `--from-stdin`, la valeur vous est demandée sans s'afficher à l'écran, et elle ne reste pas dans l'historique de votre terminal :

```bash
frankendeploy env set prod STRIPE_KEY --from-stdin
```

**Les fichiers Docker.** Au premier déploiement, FrankenDeploy écrit trois fichiers dans votre projet : `Dockerfile`, `docker-entrypoint.sh` et `.dockerignore`. Ils sont à vous : commitez-les avec `frankendeploy.yaml`. FrankenDeploy ne les écrasera **jamais**, vous pourrez donc les adapter si un jour vous en avez besoin.

**L'envoi du code.** FrankenDeploy envoie votre dossier de projet tel qu'il est sur votre disque, modifications non commitées comprises, **sauf ce que votre `.gitignore` exclut** : vos fichiers `.env` locaux, une base de test, une sauvegarde qui traîne restent sur votre machine.

**La construction de l'image.** Sur le serveur, Docker suit le Dockerfile pour fabriquer l'**image** de votre application : PHP, votre code, ses dépendances et ses assets, le tout dans une seule « boîte ». Quand cette boîte tourne, on l'appelle un **conteneur**.

**`Initialized shared data/ with 2 file(s) committed to Git`.** Le dossier partagé `data/` était vide sur le serveur. FrankenDeploy y a copié la base de données livrée dans le dépôt de la Symfony Demo. Il ne le fait qu'une fois : ensuite, `data/` contient vos vraies données, et il n'y touche plus. Pour cette copie, il ne prend **que des fichiers commités dans Git** : votre base de test locale ne risque pas de partir en production par ce chemin.

**La mise à jour sans coupure** (le « blue-green » de la sortie). C'est le principe présenté en partie 1 : la nouvelle version démarre à côté de l'ancienne, et FrankenDeploy vérifie qu'elle répond (le « health check »). Seulement si c'est le cas, il lui envoie les visiteurs. Sinon, il la supprime, et l'ancienne n'a jamais cessé de fonctionner.

**Le HTTPS** : Caddy a obtenu le certificat auprès de Let's Encrypt au passage. Il le renouvellera de lui-même, vous n'aurez pas à y penser.

Le **premier** déploiement prend quelques minutes, surtout pour construire l'image : installer PHP et ses extensions prend du temps, plus ou moins selon la puissance du serveur. Les fois suivantes, Docker réutilise ce qui n'a pas changé, et ça va beaucoup plus vite.

### Vérifier

Ouvrez `https://demo.yoandev.co` : la page d'accueil de la Symfony Demo s'affiche, avec le cadenas. Allez ensuite sur **le blog** : la liste des articles s'affiche, la base de données est donc bien en place.

Pour voir ce qui se passe sur le serveur (les visites, les erreurs éventuelles) :

```bash
frankendeploy logs prod -f
```

### Et ensuite

Vous modifiez le code, et vous redéployez avec la même commande (commiter avant reste une bonne habitude, mais ce n'est pas obligatoire) :

```bash
frankendeploy deploy prod
```

Plus de questions cette fois : vos réponses ont été retenues, et `APP_SECRET` est déjà sur le serveur. Le déploiement est **nettement plus rapide** que le premier. Et le blog garde ses données.

Quelques commandes du quotidien :

```bash
frankendeploy logs prod -f                                  # suivre les logs
frankendeploy env set prod MAILER_DSN --from-stdin --reload # ajouter un secret et redémarrer
frankendeploy exec prod php bin/console about               # lancer une commande dans le conteneur
frankendeploy app status prod                               # l'état de l'application
```

Et si votre projet utilise PostgreSQL ou MySQL, FrankenDeploy installe la base de données sur le serveur, la relie à votre application, et **la sauvegarde avant chaque modification de sa structure**. Même principe : `init`, `deploy`, rien à configurer à la main.

---

## Ce que FrankenDeploy ne fait pas

FrankenDeploy vise une situation précise : une application Symfony, un VPS, une petite équipe ou une personne seule. En dehors :

- **Un seul serveur par application** : si votre site reçoit plus de visiteurs qu'un serveur ne peut en accueillir, ou s'il ne doit jamais tomber même quand le serveur tombe, il vous faudra une solution plus lourde (j'ai écrit sur [FrankenPHP en production sur k3s](/symfony-frankenphp-k3s-production)).
- **La base de données est sur le même serveur**, et ses sauvegardes aussi. Si le serveur disparaît, tout disparaît : copiez régulièrement les sauvegardes ailleurs.
- **Ubuntu ou Debian uniquement** côté serveur, volontairement. Les autres systèmes sont refusés.
- **L'outil est jeune** (versions 0.x) : son comportement peut encore évoluer d'une version à l'autre. Chaque changement est documenté.

La [page des limites](https://yoanbernabeu.github.io/frankendeploy/limits/) les détaille toutes. Un bon outil pour débuter, c'est aussi un outil qui dit clairement ce qu'il ne fait pas.

---

## Conclusion

Cinq commandes, deux réponses, quelques minutes : la Symfony Demo est en ligne, en HTTPS, derrière un pare-feu, avec des mises à jour sans coupure. Et rien de magique : un Dockerfile lisible dans votre projet, un fichier de configuration commité avec votre code, et un serveur Ubuntu tout à fait ordinaire, que vous pouvez inspecter quand vous voulez.

Le déploiement ne devrait plus être un rite de passage.

Si vous l'essayez, sur la Symfony Demo ou sur votre propre projet, [les retours et les issues sont les bienvenus](https://github.com/yoanbernabeu/frankendeploy/issues).
