VPSにActual BudgetをDocker Composeで構築する方法
Docker ComposeでActual BudgetをVPSに構築します。データボリュームの設定、ブラウザーにHTTPSが必要な理由、最初の予算ファイル、銀行データのインポート、バックアップまで解説します。
構築するもの
Actual Budgetはセルフホスト型のエンベロープ予算管理アプリです。自分でホストできるYNABの代替製品を探す場合、通常はこのアプリが候補になります。サーバーは1つのコンテナ、1つのデータボリューム、1つのHTTPS名で構成されます。通常の予算管理に必要な処理は、最小構成のVPSでも十分に実行できます。サーバーが主にファイルを保存し、同期するだけだからです。
コマンドを入力する前に、アーキテクチャを理解しておくと役立ちます。予算データ自体はSQLiteデータベースで、ブラウザー内と各モバイルアプリ内に保存されます。これからインストールするサーバーは同期エンドポイントです。アカウント一覧、予算ファイル、スマートフォンとノートパソコンの内容を一致させる変更ログを保持します。そのため、サーバーが停止していてもアプリは動作します。また、いずれかのクライアントにコピーが残っていれば、サーバーを失っても予算データは失われません。
サーバーに HTTPS が必要な理由
Actual は HTTPS を要求します。これは形式的な要件ではありません。ブラウザーは、Actual がエンドツーエンド暗号化に使用するインターフェースである Web Crypto API を、仕様で secure context と呼ばれる環境でのみ公開します。secure context は https:// または http://localhost です。別のマシンのブラウザーで http://203.0.113.10:5006 からアプリを読み込むと、ブラウザーがその機能をページに渡さないため、これらの機能は利用できません。公式モバイルビルドも、通常の http:// サーバー URL を拒否します。
実用的な構成は2つあります。このガイドのように、コンテナの前段に実在する名前で有効な証明書を設定します。または、プロジェクトのドキュメントに記載されている ACTUAL_HTTPS_KEY と ACTUAL_HTTPS_CERT を使用して、サーバーに自己署名証明書を設定し、すべてのデバイスで毎回ブラウザー警告を受け入れます。Let's Encrypt の無料証明書は5分で取得できるため、前者を選択してください。
Docker ComposeでActual Budgetをインストールする
新しいサーバーの場合は、最初にDockerをインストールしてください。Composeファイルの構文に慣れていない場合は、VPS向けDocker Composeの基本ガイドで、以下で使用するフィールドを説明しています。
sudo install -d -m 755 /opt/actual
sudo install -d -m 700 /opt/actual/data/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このファイルでは、3つの点が重要です。
イメージはactualbudget/actual-server:latestです。プロジェクトがDocker Hubに公開し、ghcr.io/actualbudget/actualにもミラーリングしています。低消費電力のマシン向けにlatest-alpineタグがあります。
コンテナは/data以下にすべてを書き込みます。その中にはserver-filesがあり、ログイン情報とセッションTokenを含むaccount.sqliteを保持します。また、予算ファイル自体を保持するuser-filesもあります。このパスをマウントしないと、次のdocker compose pullで予算が破棄されます。ACTUAL_DATA_DIRで移動できますが、デフォルトのままで問題ありません。
ポートは127.0.0.1でのみ公開します。単独の5006:5006はすべてのインターフェースで公開します。また、Dockerは独自のルールをufwより優先して書き込むため、すべて拒否するファイアウォールを設定していても、アプリケーションがインターネットに公開されます。この意外な動作については、Dockerで公開したポートがufwを迂回する理由で説明しています。loopbackにバインドすると、同じサーバー上のreverse proxyだけが接続できます。
起動します。
cd /opt/actual
docker compose up --detach
docker compose logs -f actualサーバーがport 5006でlistenしていることを報告すると、ログの出力は落ち着きます。DNSを変更する前に、ローカルで確認してください。
curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5006/200は、アプリケーションが応答していることを示します。curl: (7) Failed to connectは、コンテナが実行されていないことを示し、docker compose psで終了したことを確認できます。通常の原因は、マウントしたvolumeの権限問題です。ログのEACCES行で確認できます。
証明書と実際の名前を設定します
AレコードをVPS、budget.example.comに向け、名前解決を待ちます。その後、nginxをインストールして証明書を発行します。Ubuntu 24.04でnginxにCertbotを設定するガイドで、発行手順と更新タイマーを詳しく説明しています。
プロキシブロック:
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は忘れられやすい設定です。完全同期では、予算ファイル全体がアップロードされます。nginxのリクエストボディはデフォルトで1 MBに制限されています。そのため、ファイルがこのサイズを超えると同期に失敗します。nginxのアクセスログには413 Request Entity Too Largeが記録されますが、アプリケーションには一般的な同期エラーだけが表示されます。サーバーには別の制限もあります。ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MBのデフォルト値は20で、ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MBのデフォルト値は50です。利用する制限値のうち大きい方を超えるように、nginxの制限を設定してください。
設定をリロードしてテストします。
sudo nginx -t && sudo systemctl reload nginx
curl -fsS -o /dev/null -w '%{http_code}\n' https://budget.example.com/初回起動: パスワードと最初の予算ファイル
ブラウザーで https://budget.example.com を開きます。最初の画面で、サーバーのパスワードを設定します。この1つのパスワードでサーバー全体を保護するため、長くランダムなものを生成し、自分でホストする Vaultwarden パスワードマネージャーなど、後で見つけられる場所に保管します。作成するユーザーアカウントはありません。Actual のサーバーは設計上、1つのパスワードだけを使用します。そのため、予算を共有することは、そのパスワードを共有することを意味します。
次に、予算ファイルを作成します。Actual でエンドツーエンド暗号化を有効にするかどうかを確認されたら、はいを選択します。これにより、サーバーには暗号文だけが保存されます。レンタルマシンで財務データを扱う場合は、これが適切な設定です。ただし、代償があります。暗号化パスワードはサーバーに送信されないため、失うとファイルは復元できず、リセット手段もありません。その画面を先に進む前に、パスワードを書き留めてください。
開始残高には、過去数年分の履歴をインポートするのではなく、銀行の現在の残高を設定します。エンベロープ予算は現在手元にある資金を起点に進めるため、履歴が空でも問題はありません。
取引を取り込む
ここでは、熱意よりも正直さが重要です。インポートの仕組みが、セルフホスト型の家計管理から離れる主な理由だからです。
手入力が基本であり、常に機能します。封筒方式では、手入力こそが目的だとも言えます。購入内容を入力することで、支出を意識できるからです。
大量の取引にはファイルインポートを使います。Actual は CSV、QIF、OFX、QFX を読み込めます。どの銀行も、少なくともいずれか1つの形式でエクスポートできます。アカウント画面からアカウントごとにインポートし、列の対応付けを1回行います。Actual は、そのアカウント用のレイアウトを記憶します。
銀行との自動同期も利用できます。ただし、サーバーが単独で銀行と通信することはできないため、サードパーティーのサービスが必要です。Actual は、北米の銀行向けに SimpleFIN Bridge、ヨーロッパ向けに Enable Banking、ニュージーランド向けに Akahu、ブラジル向けに Pluggy.ai をサポートしています。GoCardless も引き続きサポートされていますが、新規アカウントは受け付けていません。利用者自身でプロバイダーに登録し、認証情報を生成して、サーバーに追加します。2026年7月時点で、SimpleFIN Bridge は最大25の金融機関に対して年間15 US dollarsを請求します。他のサービスの料金体系は異なります。
この機能に依存する前に、2つの制限を理解しておいてください。API の認証情報はサーバー上に保存され、エンドツーエンド暗号化の対象外です。サーバーが認証情報を使用する必要があるためです。また、Actual は定期的にポーリングしません。同期はボタンを押して実行する操作であり、バックグラウンドジョブではありません。
バックアップはファイルだけなので必要です
必要なデータはすべて /opt/actual/data 配下にあります。エクスポート処理は不要で、スクリプトで実行するデータベースダンプもありません。
注意が必要なのは SQLite です。サーバーが account.sqlite に書き込んでいる間にコピーすると、トランザクションが完了していない状態で取得されることがあります。復元を試すまで、その問題には気付けません。コピーにかかる数秒間だけコンテナを停止します。
cd /opt/actual
docker compose stop
restic -r sftp:backup@backup.example.com:/srv/restic backup /opt/actual/data
docker compose startVPSでのresticバックアップの手順でスケジュールを設定してください。この手順では、リポジトリの設定、保持期間、復元テストを扱います。復元テストを実行してください。一度も復元していないバックアップは、推測にすぎません。
Actual 自体のクライアント側バックアップは別の機能で、知っておく価値があります。ブラウザーは予算ファイルの最近のコピーを保持しており、ファイルメニューから利用できます。これにより、サーバーに触れずに「誤ってカテゴリを削除した」という問題に対処できます。
サーバーの更新
cd /opt/actual
docker compose pull
docker compose up --detachComposeは新しいイメージからコンテナを再作成し、同じボリュームを再接続するため、データは保持されます。クライアントも更新してください。サーバーとアプリのバージョンは近い状態に保つ必要があります。サーバーより大幅に古いクライアントは、バージョン不一致のメッセージを表示して同期を拒否することがあります。メジャーバージョンを上げる前にバックアップを作成してください。初回起動時にマイグレーションが実行され、ダウングレードの手段がないためです。
問題が発生した場合と確認できる内容
アプリは読み込まれますが、同期が完了しません。 nginx のアクセスログで 413 を確認します。これは client_max_body_size が低すぎることを示します。一方、502 の場合は nginx は稼働しており、コンテナが稼働していないことを示します。
暗号化オプションが表示されない、またはモバイルアプリが URL を拒否します。 ページがセキュアコンテキストではありません。アドレスバーには、IP アドレスまたは localhost ではないホスト名を含む http:// が表示されます。回避策を取らず、証明書を修正します。
予算ファイルがこのバージョンと互換性がないというメッセージが表示されます。 クライアントとサーバーのバージョンがずれています。両方を同じリリースに更新してから、再読み込みします。
コンテナがループして再起動します。 docker compose logs actual を確認します。/data に対する権限エラーは、マウントしたディレクトリにコンテナのユーザーが書き込めないことを示します。アドレス使用中エラーは、別のプロセスがすでに loopback 上の 5006 を使用していることを示します。
初回の読み込みが遅く感じられます。 ファイルを開くと、予算ファイル全体がブラウザーにダウンロードされます。最初に大きな転送が行われ、その後はローカルで読み取ります。これはサーバーのサイズ設定の問題ではないため、RAM を追加しても変わりません。
FAQ
Actual Budget は動作に HTTPS が必要ですか?
実際には必要です。Actual のエンドツーエンド暗号化はブラウザーの Web Crypto API を使用します。ブラウザーがこの API を公開するのは、https:// または http://localhost を意味するセキュアコンテキストだけです。別のマシンから通常の HTTP で接続すると、これらの機能は利用できません。また、公式モバイルアプリは通常の HTTP サーバー URL を拒否します。実際のホスト名に Let's Encrypt 証明書を使用してください。デスクトップブラウザーだけで使用する場合は、ACTUAL_HTTPS_KEY と ACTUAL_HTTPS_CERT を使用した自己署名証明書でも構いません。
Actual は銀行取引を自動的にインポートできますか?
自分で登録するサードパーティーサービスを通じてのみ可能です。北米では SimpleFIN Bridge、ヨーロッパでは Enable Banking、ニュージーランドでは Akahu、ブラジルでは Pluggy.ai を使用できます。GoCardless もサポートされていますが、新規アカウントは受け付けていません。これらの API 認証情報はサーバー上に保存され、エンドツーエンド暗号化の対象外です。同期も手動で行うため、ボタンを押して実行します。バックグラウンドでポーリングは行われません。CSV、QIF、OFX、QFX のインポートには、サードパーティーサービスは一切必要ありません。
具体的に何をバックアップする必要がありますか?
このガイドで /opt/actual/data にマウントしているデータディレクトリです。そこにはログイン情報とセッションを含む server-files/account.sqlite と、予算ファイルを含む user-files があります。コピー中に SQLite データベースへの書き込みが行われると、一部だけ書き込まれた状態を取得する可能性があります。そのため、コピーする前にコンテナを停止してください。サーバー上で状態を保持するものは他にありません。
暗号化パスワードを失うとどうなりますか?
ファイルを復旧できなくなります。エンドツーエンド暗号化の目的どおり、パスワードがサーバーに送信されることはありません。そのため、リセット機能もサポートによる復旧手段もありません。ファイルを作成したらすぐにパスワードマネージャーへ保存し、このサーバーに依存しない場所にもコピーを保管してください。
Actual Budget にはどの程度のサーバーリソースが必要ですか?
ごくわずかです。コンテナは静的アセットとファイルを提供し、予算の計算はブラウザーで行われます。共有 vCPU 1 個と 1 GB の RAM があれば問題なく動作します。数年間の履歴を含む家庭用予算のデータディレクトリも、数十 MB に収まります。ディスク容量を圧迫するのはバックアップや他のコンテナであり、Actual ではありません。