SSD Nodes Learn Hosting plans →
Guides Matt ConnorPar Matt Connor

Installer Actual Budget sur un VPS avec Docker

Installez Actual Budget avec Docker Compose sur un VPS : volume de données, HTTPS obligatoire dans le navigateur, premier budget, imports bancaires et sauvegardes.

Ce que vous allez mettre en place

Actual Budget est une application de gestion budgétaire par enveloppes que vous hébergez vous-même. C’est généralement la solution choisie par les personnes qui cherchent une alternative à YNAB pouvant être hébergée sur leur propre serveur. Le serveur se compose d’un conteneur, d’un volume de données et d’un nom HTTPS. Toutes les fonctions nécessaires à un budget classique fonctionnent sans problème sur le plus petit VPS que vous pouvez louer, car le serveur stocke essentiellement des fichiers et les synchronise.

Vous devez comprendre l’architecture avant de saisir la moindre commande. Le budget lui-même est une base de données SQLite présente dans votre navigateur et dans chaque application mobile. Le serveur que vous allez installer est un endpoint de synchronisation : il contient la liste des comptes, les fichiers de budget et le journal des modifications qui permet au téléphone et à l’ordinateur portable de conserver les mêmes données. C’est pourquoi l’application continue de fonctionner lorsque le serveur est indisponible. C’est aussi pourquoi la perte du serveur n’entraîne pas la perte de votre budget, tant qu’un client en conserve encore une copie.

Pourquoi le serveur a besoin de HTTPS

Actual demande HTTPS, et ce n’est pas une formalité. Actual a besoin d’une fonctionnalité de navigateur appelée SharedArrayBuffer pour fonctionner, et les navigateurs ne l’activent que lorsque la page est isolée des autres origines dans ce que la spécification appelle un contexte sécurisé. Un contexte sécurisé est https:// ou http://localhost. Si vous chargez l’application depuis http://203.0.113.10:5006 dans un navigateur installé sur une autre machine, le navigateur ne transmet jamais cette fonctionnalité à la page, et le budget ne se charge donc pas. Les téléphones ne font pas exception. La FAQ d’Actual indique qu’une application iOS officielle est en phase de test et se connecte à votre propre serveur, et qu’il n’existe pas encore d’application Android officielle. Sur la plupart des téléphones, vous utilisez donc la même application web, installée sur l’écran d’accueil comme Progressive Web App, et la même règle du contexte sécurisé s’applique.

Deux configurations sont donc possibles. Placez un certificat valide pour un nom de domaine réel devant le conteneur, comme l’explique ce guide. Vous pouvez aussi fournir au serveur un certificat autosigné avec ACTUAL_HTTPS_KEY et ACTUAL_HTTPS_CERT, comme l’indique la documentation du projet, puis accepter un avertissement du navigateur sur chaque appareil. Un certificat gratuit de Let’s Encrypt s’obtient en cinq minutes : choisissez donc la première option.

Installer Actual Budget avec Docker Compose

Installez d’abord Docker si le serveur est vierge. Si la syntaxe des fichiers Compose vous est inconnue, le guide Bases de Docker Compose pour un VPS présente les champs utilisés ci-dessous.

sudo install -d -m 755 /opt/actual
sudo install -d -m 700 /opt/actual/data

Écrivez /opt/actual/docker-compose.yml :

services:
  actual:
    image: actualbudget/actual-server:latest
    container_name: actual
    restart: unless-stopped
    ports:
      - '127.0.0.1:5006:5006'
    volumes:
      - ./data:/data

Trois détails de ce fichier sont importants.

L’image est actualbudget/actual-server:latest, publiée par le projet sur Docker Hub et mise en miroir sur ghcr.io/actualbudget/actual. Il existe un tag latest-alpine pour les machines peu puissantes.

Le conteneur écrit toutes ses données sous /data. Vous y trouverez server-files, qui contient account.sqlite avec vos identifiants de connexion et vos tokens de session, ainsi que user-files, qui contient les fichiers de budget. Montez ce chemin, sinon la prochaine commande docker compose pull supprimera votre budget. ACTUAL_DATA_DIR peut le déplacer, mais la valeur par défaut convient.

Le port est publié uniquement sur 127.0.0.1. Une valeur 5006:5006 seule publie le port sur toutes les interfaces. Docker ajoute ses propres règles avant celles d’ufw. L’application serait donc accessible depuis Internet, même avec un pare-feu qui refuse tout le trafic. Cette particularité est expliquée dans pourquoi les ports publiés par Docker contournent ufw. Une liaison sur loopback signifie que seul le reverse proxy du même serveur peut y accéder.

Démarrez le conteneur :

cd /opt/actual
docker compose up --detach
docker compose logs -f actual

Le journal se stabilise lorsque le serveur indique qu’il écoute sur le port 5006. Vérifiez son accès local avant de modifier le DNS :

curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5006/

Un résultat 200 signifie que l’application répond. Un résultat curl: (7) Failed to connect signifie qu’aucun processus n’a accepté la connexion sur 127.0.0.1:5006. Exécutez docker compose ps --all, car docker compose ps simple n’affiche que les conteneurs en cours d’exécution. Si le conteneur apparaît comme arrêté, la cause habituelle est un problème de permissions sur le volume monté. Il apparaît dans le journal sous la forme d’une ligne EACCES.

Placez un certificat et un nom réel devant

Pointez un enregistrement A vers le VPS, budget.example.com, puis attendez sa résolution. Installez ensuite nginx et émettez le certificat. Le guide Certbot sur Ubuntu 24.04 avec nginx décrit en détail l’émission du certificat et le timer de renouvellement.

Le bloc proxy :

server {
    listen 443 ssl;
    http2 on;
    server_name budget.example.com;

    ssl_certificate     /etc/letsencrypt/live/budget.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/budget.example.com/privkey.pem;

    client_max_body_size 100m;

    location / {
        proxy_pass http://127.0.0.1:5006;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

client_max_body_size est la ligne que l’on oublie souvent. La synchronisation courante n’envoie que vos modifications, mais le fichier de budget complet est téléversé lorsque vous enregistrez un budget auprès du serveur pour la première fois, puis chaque fois que vous réinitialisez la synchronisation. Par défaut, nginx limite le corps des requêtes à 1 MB. Lorsque le fichier dépasse cette taille, ces téléversements complets échouent avec 413 Request Entity Too Large dans le journal d’accès nginx, tandis que l’application n’affiche qu’une erreur de synchronisation générique. Le serveur possède ses propres limites, indépendantes de nginx : ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB vaut 20 par défaut et ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB vaut 50 par défaut. Définissez donc la limite nginx au-dessus de celle qui s’applique à votre configuration.

Rechargez la configuration et testez :

sudo nginx -t && sudo systemctl reload nginx
curl -fsS -o /dev/null -w '%{http_code}\n' https://budget.example.com/

Première exécution : le mot de passe et votre premier fichier de budget

Ouvrez https://budget.example.com dans un navigateur. Le premier écran vous demande de définir un mot de passe pour le serveur. Ce mot de passe protège l’ensemble du serveur : générez-en un long et aléatoire, puis conservez-le dans un endroit où vous pourrez le retrouver, par exemple dans un gestionnaire de mots de passe Vaultwarden auto-hébergé. Par défaut, aucun compte utilisateur n’est à créer : le serveur utilise un seul mot de passe. Partager un budget revient donc à partager ce mot de passe. Des connexions distinctes sont possibles uniquement si vous connectez un fournisseur OpenID, ce qu’Actual prend en charge pour les configurations multi-utilisateurs.

Créez ensuite un fichier de budget. Actual vous demande si vous souhaitez activer le chiffrement de bout en bout. Répondez oui : le serveur ne stockera alors que du texte chiffré, ce qui convient aux données financières stockées sur une machine louée. Ce choix a une contrepartie réelle : le mot de passe de chiffrement n’est jamais transmis au serveur. Si vous le perdez, le fichier est perdu et aucune réinitialisation n’est possible. Notez-le avant de poursuivre au-delà de cet écran.

Définissez vos soldes de départ à partir des montants actuels indiqués par votre banque, plutôt que d’importer plusieurs années d’historique. La budgétisation par enveloppes part de l’argent dont vous disposez actuellement et progresse à partir de là. L’absence d’historique ne vous pénalise donc pas.

Importer les transactions

C’est ici que l’honnêteté compte davantage que l’enthousiasme, car l’importation est la principale raison pour laquelle les utilisateurs abandonnent la budgétisation auto-hébergée.

La saisie manuelle est la méthode de référence et elle fonctionne toujours. Avec la méthode des enveloppes, c’est même probablement le but, puisque saisir un achat vous oblige à le remarquer.

L’importation de fichiers prend en charge la majorité des opérations. Actual lit les formats CSV, QIF, OFX, QFX et CAMT, et chaque banque en propose au moins un. Importez les opérations compte par compte depuis l’écran du compte, puis associez les colonnes lorsque le fichier est au format CSV.

La synchronisation automatique avec les banques existe, mais elle nécessite un service tiers, car le serveur ne peut pas communiquer seul avec les banques. Actual prend en charge SimpleFIN Bridge pour les banques nord-américaines, Enable Banking pour l’Europe, Akahu pour la Nouvelle-Zélande et Pluggy.ai pour le Brésil. GoCardless reste pris en charge, mais n’accepte plus de nouveaux comptes. Vous devez vous inscrire vous-même auprès du fournisseur, générer les identifiants, puis les ajouter au serveur. En octobre 2026, SimpleFIN Bridge facture 15 dollars américains par an, hors taxes, pour un maximum de 25 établissements. Les autres fournisseurs appliquent une tarification différente.

Vous devez accepter deux limites avant de vous appuyer sur cette fonctionnalité. Les identifiants d’API sont stockés sur le serveur et ne sont pas couverts par le chiffrement de bout en bout, car le serveur doit les utiliser. De plus, aucune synchronisation n’a lieu lorsque Actual est fermé : la synchronisation automatique est désactivée par défaut et, une fois activée, elle s’exécute uniquement lorsque l’application est ouverte. Le serveur n’interroge donc jamais les banques de lui-même.

Sauvegardes, parce qu’il ne s’agit que de fichiers

Tout ce qui vous importe se trouve sous /opt/actual/data. Il n’y a aucune étape d’export ni aucun dump de base de données à automatiser.

Le seul piège concerne SQLite. Copier account.sqlite pendant que le serveur y écrit peut capturer une transaction incomplète. Vous ne le découvrirez qu’au moment de la restauration. Arrêtez le conteneur pendant les quelques secondes nécessaires à la copie :

cd /opt/actual
docker compose stop
restic -r sftp:backup@backup.example.com:/srv/restic backup /opt/actual/data
docker compose start

Planifiez cette opération en suivant la méthode décrite dans sauvegardes restic sur un VPS, qui couvre la configuration du repository, la rétention et le test de restauration. Effectuez ce test. Une sauvegarde que vous n’avez jamais restaurée n’est qu’une supposition.

Les sauvegardes automatiques d’Actual sont un mécanisme distinct qu’il est utile de connaître, mais elles existent uniquement dans l’application desktop. Celle-ci conserve jusqu’à 10 copies récentes du fichier de budget, accessibles depuis File > Load Backup. Cela permet de gérer le cas où vous avez supprimé une catégorie par erreur, sans toucher au serveur. L’application web dans un navigateur ne conserve aucune copie de ce type. Si tout le monde utilise le navigateur à la maison, la sauvegarde du serveur ci-dessus est donc la seule dont vous disposez.

Mise à jour du serveur

cd /opt/actual
docker compose pull
docker compose up --detach

Compose recrée le conteneur à partir de la nouvelle image et rattache le même volume. Les données sont donc conservées. Mettez également les clients à jour. Un client plus ancien que les données qu’il reçoit cesse d’appliquer les modifications et affiche le message « Update Required ». Effectuez une sauvegarde avant de passer à une version majeure, car les migrations s’exécutent au premier démarrage et il n’existe pas de procédure de retour arrière. Actual tolère un tag latest flottant, car son état est un répertoire de fichiers. Ce n’est pas le cas d’une application qui utilise une véritable base de données. Le guide auto-héberger Chatwoot explique les tags figés et l’export de la base à effectuer avant la mise à niveau.

Ce qui échoue et ce que vous verrez

L’application se charge, mais la synchronisation ne se termine jamais. Consultez le access log de nginx pour 413. Cela signifie que client_max_body_size est défini trop bas. Un 502 signifie en revanche que nginx n’a pas reçu de réponse exploitable de l’upstream. Le plus souvent, le conteneur est arrêté ou n’écoute pas sur 5006. Vérifiez donc docker compose ps --all et le log du conteneur en premier.

L’application ne se charge pas sur un autre appareil ou indique que SharedArrayBuffer est manquant. La page n’est pas dans un contexte sécurisé. La barre d’adresse affiche http:// avec une adresse IP ou un nom d’hôte qui n’est pas localhost. Corrigez le certificat au lieu de contourner le problème.

Un message indique que le fichier de budget n’est pas compatible avec cette version. Le fichier a été ouvert dans une version d’Actual plus récente que celle du client que vous utilisez actuellement. Mettez le client à jour vers cette version au minimum, ou restaurez une sauvegarde créée avant que la version plus récente ne modifie le fichier, puis rechargez-le.

Le conteneur redémarre en boucle. Lisez docker compose logs actual. Une erreur de permissions sur /data signifie que le répertoire monté n’est pas accessible en écriture par l’utilisateur du conteneur. Une erreur d’adresse déjà utilisée signifie qu’un autre processus utilise déjà 5006 sur loopback.

Le premier chargement semble lent. L’intégralité du fichier de budget est téléchargée dans le navigateur lorsque vous l’ouvrez. Il s’agit d’un transfert important, suivi de lectures locales. Ce n’est pas un problème de dimensionnement du serveur, et ajouter de la RAM ne changera rien.

FAQ

Actual Budget a-t-il besoin de HTTPS pour fonctionner ?

Oui, en pratique. Actual a besoin de la fonctionnalité SharedArrayBuffer du navigateur pour fonctionner, et les navigateurs ne l’activent que dans un contexte sécurisé, c’est-à-dire https:// ou http://localhost. En HTTP simple depuis une autre machine, l’application ne se chargera pas. Une application iOS officielle est en phase de bêta-test et il n’existe pas encore d’application Android officielle ; la plupart des téléphones utilisent donc la même application web et sont soumis à la même règle. (FAQ) Utilisez un certificat Let’s Encrypt sur un nom d’hôte réel, ou un certificat autosigné avec ACTUAL_HTTPS_KEY et ACTUAL_HTTPS_CERT si vous acceptez un avertissement de certificat sur chaque appareil.

Actual peut-il importer automatiquement mes transactions bancaires ?

Uniquement par l’intermédiaire d’un service tiers auquel vous vous inscrivez vous-même : SimpleFIN Bridge en Amérique du Nord, Enable Banking en Europe, Akahu en Nouvelle-Zélande ou Pluggy.ai au Brésil. GoCardless est pris en charge, mais n’accepte plus de nouveaux comptes. Ces identifiants d’API résident sur votre serveur et ne sont pas couverts par le chiffrement de bout en bout. La synchronisation automatique est désactivée par défaut et, une fois activée, ne fonctionne que lorsqu’Actual est ouvert ; rien sur le serveur n’effectue d’interrogation en arrière-plan. L’importation de fichiers CSV, QIF, OFX, QFX et CAMT ne nécessite aucun tiers.

Que dois-je sauvegarder exactement ?

Le répertoire de données monté, qui est /opt/actual/data dans ce guide. Il contient server-files/account.sqlite avec les identifiants de connexion et les sessions, ainsi que user-files avec les fichiers de budget. Arrêtez le conteneur avant la copie, car la copie d’une base de données SQLite active peut capturer une écriture partielle. Aucun autre élément du serveur ne contient d’état.

Que se passe-t-il si je perds le mot de passe de chiffrement ?

Le fichier ne peut pas être récupéré. Le mot de passe n’atteint jamais le serveur, ce qui est précisément le but du chiffrement de bout en bout ; il n’existe donc ni réinitialisation ni procédure de récupération auprès du support. Enregistrez-le dans un gestionnaire de mots de passe dès que vous créez le fichier et conservez-en une copie à un endroit qui ne dépend pas de ce même serveur.

De quelles ressources serveur Actual Budget a-t-il besoin ?

De très peu. Le conteneur sert des ressources et des fichiers statiques, tandis que les calculs du budget s’effectuent dans le navigateur. Un vCPU partagé avec 1 GB de RAM suffit à l’exécuter sans problème, et le répertoire de données d’un budget familial contenant plusieurs années d’historique reste de l’ordre de quelques dizaines de mégaoctets. La pression sur le disque vient de vos sauvegardes et de vos autres conteneurs, pas d’Actual. Si vous dimensionnez une machine qui doit également exécuter un service plus gourmand, c’est généralement un serveur photo qui fixe le minimum ; vérifiez donc la quantité réelle de RAM nécessaire à PhotoPrism et Immich avant de choisir une offre. Le même raisonnement s’applique à une stack multimédia : c’est le transcodage qui détermine l’offre, tandis qu’une interface web comme Halcyon, qui transforme une bibliothèque Jellyfin en vidéoclub des années 90 où l’on peut déambuler, coûte à peu près aussi peu de ressources qu’Actual.

Cet article vous a-t-il été utile ?