Installer Stalwart sur Coolify
Récemment, j’ai commencé à héberger moi-même mes services sur un VPS. Dans l’optique de reprendre le contrôle sur ma stack en suivant le mouvement de DHH pour quitter le cloud et d’essayer d’être le plus autonome possible.
Un ami m’a beaucoup hypé Coolify, un PaaS open-source facile à utiliser, dans la veine de Heroku/Netlify/Vercel, et le tout tourne sur Docker. Du coup je me suis dit que j’allais en host un. L’installation est super simple et il s’avère que Coolify c’est vraiment super pratique. Il y a un packaging system qui permet de choisir certains services déjà tout prêts, comme RocketChat, PenPot, démarrer des DB en 2 clics, déployer des apps depuis un repo directement. Bref les joies du PaaS tout en conservant la maîtrise de son système.
Mais tous ces services nécessitent un serveur SMTP, et en regardant ce que proposent les packages Coolify, tous les services dépendent d’AWS, aucun ne propose de serveur SMTP et moi je veux rester autonome. J’ai donc commencé mes recherches et je suis tombé sur Stalwart.
Stalwart est un serveur mail open-source tout-en-un qui supporte SMTP, IMAP, POP3 et JMAP. Il est moderne, performant et bien maintenu. En gros, tout ce qu’on peut demander quand on veut auto-héberger ses emails.
J’ai trouvé un excellent article de blog d’Aldert Vaandering sur comment le mettre en place avec Coolify. Je l’ai suivi mais ça m’a pris quelques heures de trop à mon goût pour que tout fonctionne, car l’article est un peu daté. Voici donc une mise à jour pour 2026.
Prérequis
Avant de commencer, assurez-vous d’avoir :
- Un VPS (j’utilise Kimsufi, c’est pas cher et c’est Français !)
- Coolify déjà installé et fonctionnel sur votre serveur
- Un nom de domaine avec accès à ses paramètres DNS
- Des bases en Docker et en ligne de commande
- Le port 25 ouvert sur votre serveur — certains hébergeurs cloud le bloquent par défaut pour éviter le spam :/
Créer le service Docker Compose
Comme il n’y a pas de service pré-packagé pour Stalwart sur Coolify, il va falloir tout configurer nous-mêmes.
Pour commencer, créez une nouvelle ressource de type Docker Compose Empty :

Puis copiez la configuration suivante comme fichier docker compose :
services:
stalwart:
image: 'stalwartlabs/stalwart:latest'
container_name: stalwart
networks:
- coolify
ports:
- '25:25'
- '587:587'
- '465:465'
- '143:143'
- '993:993'
- '4190:4190'
- '110:110'
- '995:995'
volumes:
- '/var/lib/stalwart:/opt/stalwart'
- '/etc/localtime:/etc/localtime:ro'
- '/data/coolify/certs:/data/certs:ro'
labels:
- traefik.enable=true
- 'traefik.http.routers.mailserver.rule=Host(`mail.VOTRE_DOMAINE.COM`) || Host(`autodiscover.VOTRE_DOMAINE.COM`) || Host(`autoconfig.VOTRE_DOMAINE.COM`) || Host(`mta-sts.VOTRE_DOMAINE.COM`) || Host(`mx.VOTRE_DOMAINE.COM`) || Host(`smtp.VOTRE_DOMAINE.COM`) || Host(`pop.VOTRE_DOMAINE.COM`) || Host(`imap.VOTRE_DOMAINE.COM`)'
- traefik.http.routers.mailserver.entrypoints=http
- traefik.http.routers.mailserver.service=mailserver
- traefik.http.services.mailserver.loadbalancer.server.port=8080
- traefik.http.routers.mailserver.tls.certresolver=letsencrypt
- traefik.http.routers.mailserver.tls=true
- 'traefik.http.routers.mailserver.tls.domains[0].main=mail.VOTRE_DOMAINE.COM'
- 'traefik.http.routers.mailserver.tls.domains[0].sans=autodiscover.VOTRE_DOMAINE.COM,autoconfig.VOTRE_DOMAINE.COM,mta-sts.VOTRE_DOMAINE.COM,mx.VOTRE_DOMAINE.COM,smtp.VOTRE_DOMAINE.COM,pop.VOTRE_DOMAINE.COM,imap.VOTRE_DOMAINE.COM'
- 'traefik.tcp.routers.smtp.rule=HostSNI(`*`)'
- traefik.tcp.routers.smtp.entrypoints=smtp
- traefik.tcp.routers.smtp.service=smtp
- traefik.tcp.services.smtp.loadbalancer.server.port=25
- traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=2
- 'traefik.tcp.routers.jmap.rule=HostSNI(`*`)'
- traefik.tcp.routers.jmap.tls.passthrough=true
- traefik.tcp.routers.jmap.entrypoints=https
- traefik.tcp.routers.jmap.service=jmap
- traefik.tcp.services.jmap.loadbalancer.server.port=443
- traefik.tcp.services.jmap.loadbalancer.proxyProtocol.version=2
- 'traefik.tcp.routers.smtps.rule=HostSNI(`*`)'
- traefik.tcp.routers.smtps.tls.passthrough=true
- traefik.tcp.routers.smtps.entrypoints=smtps
- traefik.tcp.routers.smtps.service=smtps
- traefik.tcp.services.smtps.loadbalancer.server.port=465
- traefik.tcp.services.smtps.loadbalancer.proxyProtocol.version=2
- 'traefik.tcp.routers.imaps.rule=HostSNI(`*`)'
- traefik.tcp.routers.imaps.tls.passthrough=true
- traefik.tcp.routers.imaps.entrypoints=imaps
- traefik.tcp.routers.imaps.service=imaps
- traefik.tcp.services.imaps.loadbalancer.server.port=993
- traefik.tcp.services.imaps.loadbalancer.proxyProtocol.version=2
tty: true
stdin_open: true
restart: always
volumes:
data: null
networks:
coolify:
external: true
(N’oubliez pas de remplacer VOTRE_DOMAINE par votre nom de domaine, bien entendu.)
Le point important ici, c’est que Coolify fait tourner tous vos conteneurs derrière Traefik, un reverse proxy. On veut donc que Traefik se charge de la génération des certificats TLS nécessaires plutôt que de laisser cette responsabilité à Stalwart, et qu’il gère aussi le routage de tous les services proposés par Stalwart.
C’est pourquoi on utilise l’image Docker officielle de Stalwart avec la configuration standard, à deux exceptions près :
- On connecte l’image au réseau Coolify pour qu’elle ait accès à internet et reste gérable par Coolify.
- On monte
/data/coolify/certs(là où Traefik génère les certificats) dans le répertoire/data/certs:rodu conteneur Docker (en lecture seule).
On configure aussi Traefik pour générer un certificat qui couvre tous les domaines nécessaires à Stalwart (smtp, mx, etc.). Si vous n’utilisez pas certains protocoles, n’hésitez pas à en ajouter ou en retirer.
Pensez bien à activer Connect To Predefined Network et à donner à votre serveur mail Stalwart un lien de domaine, par exemple mail.votredomaine.com (le 8080 après le nom de domaine est important, ne le retirez pas. Vous pourrez quand même ouvrir l’interface web de Stalwart sans ajouter le port dans l’URL).

Important ! Récupérez les identifiants de l’utilisateur admin et le mot de passe de l’interface web en consultant les logs après. Sinon, vous allez devoir le faire en trifouillant le fichier de config et c’est bien embêtant.
Mise en place du certificate dumper
Maintenant, on a besoin que Traefik partage les certificats qu’il crée pour qu’on puisse les utiliser dans la configuration de Stalwart. Si vous avez déjà mis en place d’autres services nécessitant des certificats, vous avez probablement déjà fait cette étape.
On va modifier les paramètres de Traefik sur votre serveur Coolify en allant dans Servers -> votre serveur -> Proxy et en ajoutant un service supplémentaire qui extrait et exporte les certificats générés dans le fichier acme.json de Traefik.
Les lignes importantes pour faire fonctionner le tout avec Stalwart sont les suivantes :
traefik:
{...}
command:
{...}
# La ligne suivante indique où Traefik stocke les certificats générés :
- '--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json'
# Ce service extrait les certificats du fichier ci-dessus vers /output dans le conteneur
traefik-certs-dumper:
image: ghcr.io/kereis/traefik-certs-dumper:latest
container_name: traefik-certs-dumper
restart: unless-stopped
depends_on:
- traefik
volumes:
- /etc/localtime:/etc/localtime:ro
- /data/coolify/proxy:/traefik:ro
- /data/coolify/certs:/output
# ce volume est lié à la sortie, pour qu'on puisse ensuite utiliser les certificats dans Stalwart
Configurer Stalwart pour utiliser les certificats
On est presque au bout. Il ne reste plus qu’à mettre à jour la configuration.
Si tout s’est bien passé, vos certificats devraient maintenant être générés et montés dans Stalwart. Sauf que Stalwart ne les connaît pas encore. Pour corriger ça, il faut faire un peu de configuration manuelle (en tout cas, je n’ai pas encore trouvé comment faire ça depuis l’interface web).
Ouvrez un terminal sur votre serveur (via SSH ou via l’interface web dans Servers -> votre serveur -> Proxy).
Allez dans /var/lib/stalwart/etc et modifiez config.toml en ajoutant ceci à la fin :
certificate.default.cert = "%{file:/data/certs/mail.VOTRE_DOMAINE.com/cert.pem}%"
certificate.default.default = true
certificate.default.private-key = "%{file:/data/certs/mail.VOTRE_DOMAINE.com/key.pem}%"
Attention — si votre config.toml utilise le format table, ajoutez-le plutôt de cette façon :
[certificate."default"]
default = true
cert = "%{file:/data/certs/mail.VOTRE_DOMAINE.com/cert.pem}%"
private-key = "%{file:/data/certs/mail.VOTRE_DOMAINE.com/key.pem}%"
Tant que vous y êtes, configurez aussi votre hostname en haut du fichier :
lookup.default.hostname = "mail.VOTRE_DOMAINE.com"
Une fois que c’est fait, redémarrez Stalwart dans Coolify.
Si tout fonctionne correctement, vous devriez retrouver votre certificat par défaut dans Settings -> Network -> TLS.
Configurer votre serveur de noms
Ouvrez l’interface web de Stalwart (mail.VOTRE_DOMAINE.com ou le sous-domaine que vous avez choisi) et connectez-vous avec les identifiants récupérés dans les logs ou dans votre config.toml.
Allez dans Directory -> Domains et cliquez sur Create domain. Pour le nom de domaine, utilisez simplement VOTRE_DOMAINE.com (ce qui est censé venir après le @ dans vos adresses email). Pas besoin de remplir autre chose — cliquez simplement sur save.
Ensuite, cliquez sur les trois points à côté de votre domaine fraîchement créé, puis sur DNS records, et ajoutez ces enregistrements dans vos paramètres DNS via le tableau de bord de votre fournisseur de nom de domaine.
Attention : si votre WebAdmin Stalwart est sur mail.VOTRE_DOMAINE.com (comme dans ce tutoriel), pensez bien à configurer l’enregistrement CNAME mail sous peine de perdre l’accès à votre WebAdmin.
Et voilà — vous venez de configurer votre serveur Stalwart et vous êtes prêt à envoyer et recevoir des mails !
Si ça ne marche pas
Vérifiez que votre pare-feu est correctement configuré. Tous les ports spécifiés dans le fichier docker-compose.yml doivent être ouverts. N’oubliez pas que le port 25 en particulier est souvent bloqué par les hébergeurs — vérifiez auprès du vôtre si les mails sortants ne fonctionnent pas.
Amusez-vous bien avec Stalwart !
← Retour au blog