Déployer Flask sur DigitalOcean
Suivez ça sur votre propre serveur. Commencer gratuitement, sans carte
flask run est un serveur de développement par conception, la documentation officielle de Flask le dit directement, et il n’est pas fait pour servir du vrai trafic. Mettre une application Flask en production signifie choisir un serveur WSGI, le pointer vers le bon module, provisionner une base de données, et décider comment les migrations s’exécutent, des décisions qu’un framework minimal comme Flask laisse délibérément à l’application plutôt que d’assumer une seule convention comme pourrait le faire un framework plus large. Ce guide fait tout ça dans Docker sur un Droplet DigitalOcean, avec Caddy gérant le HTTPS, et termine avec une application Flask sur votre propre domaine que vous saurez redéployer.
Ce qu’il vous faut
- Un Droplet DigitalOcean. Le plan Basic à 2 Go de mémoire, 1 vCPU, 50 Go de disque (12 $/mois) fait tourner confortablement une seule application Flask avec Postgres à côté ; le palier 1 Go (6 $/mois) fonctionne pour une application à faible trafic sans grande marge.
- Un domaine, avec le DNS déjà pointé vers le Droplet avant de demander le HTTPS.
- Un compte DigitalOcean.
- Une application Flask dans un dépôt Git. Ce guide utilise Flask 3.1.3, qui requiert Python 3.9 ou plus récent ; le Dockerfile ci-dessous fixe Python 3.13.
- Docker et le plugin Compose, soit localement pour tester le build d’abord, soit directement sur le Droplet.
Étape 1 : créer le Droplet
Dans le panneau de contrôle DigitalOcean, Create → Droplets : choisissez Ubuntu 24.04, la région la plus proche de vos utilisateurs, le plan Basic à 2 Go/1 vCPU, ajoutez votre clé SSH, et créez-le.
ssh root@your-droplet-ip
Étape 2 : installer Docker
curl -fsSL https://get.docker.com | sh
docker --version
docker compose version
Étape 3 : écrire le Dockerfile
FROM python:3.13-slim
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "app:app"]
La documentation officielle de déploiement de Flask pointe directement vers gunicorn : « il s’intègre facilement avec les plateformes d’hébergement » et n’a besoin d’aucune dépendance supplémentaire à installer. --workers 4 est un point de départ raisonnable (une règle empirique courante de la documentation de gunicorn est environ 2 * cœurs CPU + 1) ; ajustez selon le nombre réel de vCPU du Droplet. Si votre application utilise une app factory plutôt qu’une variable app au niveau du module, la commande devient plutôt gunicorn --bind 0.0.0.0:8000 'app:create_app()'.
Un requirements.txt minimal :
flask>=3.1
gunicorn>=23.0
psycopg[binary]>=3.2
flask-sqlalchemy>=3.1
flask-migrate>=4.0
Étape 4 : écrire le fichier Compose
services:
app:
build: .
restart: unless-stopped
env_file:
- .env
depends_on:
db:
condition: service_healthy
expose:
- "8000"
db:
image: postgres:18-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
timeout: 5s
retries: 5
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
depends_on:
- app
volumes:
db-data:
caddy-data:
caddy-config:
Votre fichier .env :
DB_NAME=app
DB_USER=app
DB_PASSWORD=change-this
SECRET_KEY=change-this-too
FLASK_ENV=production
DATABASE_URL=postgresql+psycopg://app:change-this@db:5432/app
Validez avant de démarrer :
docker compose config
Étape 5 : écrire le Caddyfile
app.example.com {
reverse_proxy app:8000
}
Étape 6 : premier démarrage et migrations
docker compose up -d
docker compose exec app flask db upgrade
docker compose ps
Vous devriez voir app, db et caddy tous running, et https://app.example.com devrait répondre avec un certificat valide.
Étape 7 : déployer de nouvelles versions
git pull
docker compose build app
docker compose up -d --no-deps app
docker compose exec app flask db upgrade
Reconstruire seulement app laisse db et caddy intacts, donc le certificat et la base de données restent tous deux en marche pendant le déploiement.
Sauvegardes
docker compose exec -T db pg_dump -U app app | gzip > backup-$(date +%F).sql.gz
Copiez le fichier hors du Droplet via une tâche cron, et testez une restauration dans une base de données jetable à l’occasion pour savoir que ça fonctionne réellement.
Servir les fichiers statiques
Flask peut servir son propre dossier static/ directement, mais Caddy est déjà devant chaque requête et est mieux placé pour ça que de demander aux workers gunicorn de passer du temps à servir des fichiers plutôt qu’à exécuter la logique applicative. Ajoutez un correspondant avant le proxy fourre-tout :
app.example.com {
handle_path /static/* {
root * /srv/static
file_server
}
reverse_proxy app:8000
}
Montez le répertoire statique de l’application dans un volume que Caddy peut aussi lire, et exécutez la commande que votre projet utilise pour collecter ou copier les ressources statiques vers ce chemin pendant le build Docker, pour que les fichiers existent avant que le conteneur Caddy commence à les chercher.
Le raccourci : faites-le avec Railyard
Déployer Flask sur DigitalOcean exécute la même recette à partir d’une URL de dépôt. Connectez un compte DigitalOcean avec un jeton ayant accès en écriture aux Droplets, ou amenez un serveur déjà provisionné. Railyard détecte Flask à partir de requirements.txt ou pyproject.toml, sert app:app sur $PORT avec gunicorn (pointé vers votre propre module s’il se trouve ailleurs), et reconnaît psycopg comme dépendance pour provisionner Postgres avec DATABASE_URL injectée automatiquement. Définissez flask db upgrade ou alembic upgrade head comme commande de release pour migrer à chaque mise en production, comme à l’étape 6 ci-dessus mais exécuté automatiquement avant que chaque nouvelle version reçoive du trafic. RQ est proposé comme worker quand c’est une dépendance, et tout autre processus du Procfile en devient un aussi. Voyez la documentation Flask pour les règles exactes de détection.
Dépannage
Connection refusedau premier démarrage :appa démarré avant que Postgres ait fini de s’initialiser ; la condition de healthcheck à l’étape 4 devrait l’empêcher, mais une courte boucle de réessai autour de la première commande de migration est une protection supplémentaire raisonnable.- gunicorn démarre mais les requêtes bloquent : habituellement un nombre de workers trop élevé pour le CPU réel du Droplet, ou un appel bloquant dans un gestionnaire de requête qui affame les autres workers ; vérifiez
docker statssous charge et réduisez--workersavant d’ajouter plutôt une taille de Droplet. sqlalchemy.exc.OperationalErrormême si le conteneur de base de données tourne : confirmez queDATABASE_URLdans.envcorrespond bien aux identifiants du conteneurdb; un.envpérimé laissé du développement local est la cause la plus courante.- Le HTTPS ne s’émet jamais, Caddy continue de réessayer : le DNS ne pointe pas encore vers le Droplet.
dig +short app.example.comdevrait correspondre à l’IP du Droplet. flask db upgradeéchoue avec « Can’t locate revision » : le dossiermigrations/n’a pas été commité au dépôt, ou l’image a été construite à partir d’un checkout périmé manquant un fichier de migration récent ; confirmez quegit statusest propre sur le serveur avant de reconstruire.- Les ressources statiques retournent 404 via Caddy mais fonctionnent en frappant gunicorn directement : le correspondant
handle_pathde la section fichiers statiques ci-dessus n’a pas correspondu au chemin de la requête, habituellement parce que le préfixe d’URL statique réel de l’application diffère de/static/; vérifiez quel préfixe l’application génère réellement dans le HTML rendu avant d’assumer que Caddy est mal configuré.
FAQ
Pourquoi gunicorn plutôt que le serveur intégré de Flask ? La documentation de Flask est explicite : le serveur de développement n’est pas conçu pour être particulièrement sécurisé, stable ou efficace ; gunicorn est un serveur WSGI de production fait exactement pour ça, sans dépendance supplémentaire à compiler.
Et les routes Flask async ? Flask supporte les fonctions de vue async def depuis Flask 2.0, mais le worker sync par défaut de gunicorn ne fait pas tourner de boucle d’événements ; si l’application a vraiment besoin de gestion async à l’échelle, la classe de worker gevent de gunicorn ou passer à un framework basé sur ASGI sont les deux vraies options, pas quelque chose que le worker par défaut de ce guide gère bien.
Postgres ou MySQL ? Flask-SQLAlchemy est agnostique à la base de données ; ce guide utilise Postgres, mais remplacer l’image db et le schéma de l’URI de connexion SQLAlchemy fonctionne de la même façon pour MySQL.
Ai-je vraiment besoin d’un Droplet à 2 Go ? Un Droplet à 1 Go fait tourner Flask et Postgres ensemble, mais avec très peu de marge mémoire pour des pics de trafic ou un second processus ; 2 Go est une base plus réaliste dès que de vrais utilisateurs se présentent.
Et si mon application a besoin d’un worker de fond, pas seulement du processus web ? Ajoutez un processus de style Procfile (worker: rq worker --url $REDIS_URL par exemple) comme son propre service dans le fichier Compose, construit depuis la même image que app, avec son propre command en remplacement, plutôt que de le faire tourner dans le même conteneur que le processus web.
Comment voir les journaux et les métriques de base sans me connecter en SSH à chaque fois ? Le tableau de bord du Droplet de DigitalOcean montre les graphiques de CPU, mémoire et disque dès le départ sans configuration supplémentaire ; pour les journaux applicatifs, docker compose logs -f app via SSH reste l’option la plus directe à moins de les acheminer vers un agrégateur de journaux externe, ce qui vaut la peine dès que plus d’une personne a besoin de vérifier les journaux régulièrement.
Une application Flask sur un Droplet DigitalOcean que vous avez provisionné vous-même coûte 12 $ par mois, avec gunicorn, Postgres et les migrations explicites dans des fichiers que vous contrôlez plutôt qu’assumés. Expédier une nouvelle version une fois qu’ils existent, c’est reconstruire un conteneur et exécuter une commande de migration, rien d’autre à retenir.
Pourquoi les gens choisissent Railyard pour ça
- Votre propre compte infonuagique. Déployez sur DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou n'importe quel serveur Linux par SSH, facturé directement par votre fournisseur, sans majoration.
- Un prix fixe par équipe. L'offre gratuite est gratuite pour toujours : un serveur plus un serveur de base de données, trois applications, sans carte. Pro coûte 19 $ par mois pour jusqu'à cinq serveurs et un nombre illimité d'applications. Team coûte 49 $ par mois avec des serveurs illimités. Aucuns frais par application, par déploiement, par siège ou par gigaoctet.
- Des gabarits en un clic. Déployez des applications open source, comme des bases de données, des outils d'analyse et des outils de développement, sur votre propre serveur en un clic.
- Des bases de données gérées. Postgres, MySQL et Redis, avec des sauvegardes planifiées, vérifiées par restauration, et la récupération à un point dans le temps.
- Déploiement par push. Déployez depuis GitHub, GitLab ou la CLI, avec HTTPS automatique et des mises en production sans interruption.