SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-10-28

VPSにActual BudgetをDocker Composeで構築する方法

Docker ComposeでActual BudgetをVPSに構築します。データボリューム、ブラウザーにHTTPSが必要な理由、初回の予算ファイル、銀行データのインポート、バックアップ方法を解説します。

構築するもの

Actual Budget はセルフホスト型の封筒式予算管理アプリです。自分でホストできる YNAB の代替を探す場合、一般的な選択肢になります。サーバーは 1 つのコンテナ、1 つのデータボリューム、1 つの HTTPS 名で構成されます。通常の予算管理に必要な処理は、借りられる最小構成の VPS でも十分に動作します。サーバーが主にファイルの保存と同期を行うためです。

コマンドを入力する前に、アーキテクチャを理解しておく必要があります。予算データ自体は SQLite データベースです。このデータベースはブラウザー内と各モバイルアプリ内に保存されます。これからインストールするサーバーは同期エンドポイントです。アカウント一覧、予算ファイル、変更ログを保持し、スマートフォンとノートパソコンの状態を一致させます。そのため、サーバーが停止してもアプリは動作します。また、いずれかのクライアントにコピーが残っている限り、サーバーを失っても予算データは失われません。

サーバーに HTTPS が必要な理由

Actual は HTTPS を要求するため、単なる形式ではありません。Actual は SharedArrayBuffer と呼ばれるブラウザー機能を実行時に必要とします。この機能は、仕様上のセキュアコンテキストでページがクロスオリジン分離されている場合に限り、ブラウザーで有効になります。セキュアコンテキストとは、https:// または http://localhost です。別のマシンのブラウザーで http://203.0.113.10:5006 からアプリを読み込むと、ブラウザーはその機能をページに提供しません。そのため、予算が読み込まれません。スマートフォンでも例外はありません。Actual の FAQ によると、公式 iOS アプリはベータテスト中で、自分のサーバーに接続します。また、公式 Android アプリはまだありません。そのため、多くのスマートフォンでは同じ Web アプリをホーム画面に Progressive Web App としてインストールして使用します。この場合も、同じセキュアコンテキストのルールが適用されます。

実用的な構成は 2 つあります。1 つ目は、コンテナの前段にある実際の名前へ正式な証明書を設定する方法です。このガイドではこの方法を使用します。2 つ目は、プロジェクトのドキュメントに記載されている ACTUAL_HTTPS_KEY と ACTUAL_HTTPS_CERT を使って、サーバーに自己署名証明書を設定する方法です。この場合、すべてのデバイスでブラウザーの警告を受け入れる必要があります。Let's Encrypt の無料証明書は 5 分で取得できるため、1 つ目の方法を選択してください。

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 と、予算ファイル自体を保持する user-files があります。account.sqlite を含むこのパスをマウントしないと、次の docker compose pull で予算が失われます。ACTUAL_DATA_DIR で移動できますが、デフォルトのままで問題ありません。

ポートは 127.0.0.1 のみで公開します。単独の 5006:5006 はすべてのインターフェースで公開します。また、Docker は ufw より先に独自のルールを書き込むため、すべて拒否するファイアウォールを設定していてもアプリケーションがインターネットに公開されます。この挙動については、Docker の公開ポートが ufw を迂回する理由で説明しています。loopback にバインドすると、同じサーバー上のリバースプロキシだけが接続できます。

起動します。

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

サーバーがポート 5006 で待ち受けていると報告すれば、ログは安定します。DNS を変更する前に、ローカルから確認します。

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

200 なら、アプリケーションは応答しています。curl: (7) Failed to connect なら、127.0.0.1:5006 で接続を受け付けたプロセスがありません。docker compose ps --all を実行してください。通常の docker compose ps は、実行中のコンテナだけを一覧表示するためです。コンテナが exited と表示される場合、通常の原因はマウントしたボリュームの権限問題です。ログには EACCES 行として表示されます。

証明書と正式なドメイン名を前段に設定する

VPS に A レコードを向け、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 パスワードマネージャーなど、後で見つけられる場所に保管します。初期状態では、作成するユーザーアカウントはありません。サーバーのパスワードは1つだけなので、予算を共有することは、そのパスワードを共有することを意味します。個別のログインを使えるのは、OpenID プロバイダーを接続した場合だけです。Actual はマルチユーザー構成に対応しています。

次に、予算ファイルを作成します。Actual はエンドツーエンド暗号化を有効にするかどうかを尋ねます。ここでは有効にします。サーバーには暗号文だけが保存されるため、レンタルマシンで金融データを扱う場合に適しています。ただし、代償があります。暗号化パスワードはサーバーに送信されないため、紛失するとファイルを失い、リセットもできません。その画面を先へ進む前に、パスワードを書き留めてください。

開始残高には、何年分もの履歴をインポートするのではなく、銀行の現在の残高を設定します。封筒予算は現在手元にある資金を起点に進めるため、履歴が空でも問題ありません。

取引データを取り込む

ここでは、熱意よりも正直さが重要です。インポートの使い勝手が、セルフホスト型の家計管理から離れる主な理由だからです。

手入力が基本であり、常に利用できます。封筒方式では、購入内容を入力することで支出を意識できるため、手入力こそが目的ともいえます。

大量のデータはファイルインポートで処理できます。Actual は CSV、QIF、OFX、QFX、CAMT を読み込めます。各銀行は、少なくともいずれか1つの形式でエクスポートできます。アカウント画面からアカウントごとにインポートし、ファイルが CSV の場合は列を対応付けます。

銀行との自動同期も利用できます。ただし、サーバーが単独で銀行と通信することはできないため、サードパーティーサービスが必要です。Actual は、北米の銀行向けに SimpleFIN Bridge、欧州向けに Enable Banking、New Zealand 向けに Akahu、Brazil 向けに Pluggy.ai をサポートしています。GoCardless も引き続きサポートされていますが、新規アカウントは受け付けていません。各プロバイダーに自分で登録し、認証情報を生成してサーバーに追加します。SimpleFIN Bridge の料金は、2026年10月時点で最大25の金融機関に対して年間15 US dollars plus tax です。その他のサービスは料金体系が異なります。

利用を前提にする前に、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 start

VPS 上の restic バックアップの手順でスケジュールを設定します。この手順では、リポジトリのセットアップ、保持期間、復元訓練を扱います。復元訓練を実施してください。一度も復元していないバックアップは、推測にすぎません。

Actual 自身の自動バックアップは別の機能で、知っておく価値があります。ただし、利用できるのはデスクトップアプリだけです。予算ファイルの最新のコピーを最大 10 個保持し、File > Load Backup から利用できます。これにより、「誤ってカテゴリを削除した」という事態にも、サーバーに触れずに対処できます。ブラウザーの Web アプリはこのようなコピーを保持しません。そのため、家の全員がブラウザーを使う場合、上記のサーバーバックアップが唯一のバックアップになります。

サーバーの更新

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

Compose は新しいイメージからコンテナを再作成し、同じボリュームを再接続するため、データは保持されます。クライアントも更新してください。受信するデータより古いクライアントは変更の適用を停止し、「Update Required」と表示します。メジャーバージョンを上げる前にバックアップを取得してください。初回起動時にマイグレーションが実行され、ダウングレードの方法がないためです。Actual は状態をファイルのディレクトリとして保持するため、浮動する latest タグでも問題になりにくい一方、実データベースを使用するアプリケーションでは事情が異なります。Chatwoot のセルフホスティングでは、固定タグの使用方法と、更新前に必要なダンプの取得手順を説明しています。

何が問題になり、何を確認できるか

アプリは読み込まれますが、同期が完了しません。 nginx のアクセスログで 413 を確認します。これは client_max_body_size の設定値が低すぎることを示します。一方、502 の場合は、nginx が upstream から使用可能な応答を受け取れていません。多くの場合、コンテナが停止しているか、5006 で待ち受けていないことが原因です。まず docker compose ps --all とコンテナのログを確認します。

別のデバイスでアプリを読み込めない、または SharedArrayBuffer がないと表示されます。 ページが secure context ではありません。アドレスバーには、IP アドレスまたは localhost ではないホスト名を使用した http:// が表示されます。回避策を取らず、証明書を修正します。

予算ファイルがこのバージョンと互換性がないというメッセージが表示されます。 ファイルが、現在使用しているクライアントより新しい Actual のバージョンで開かれています。クライアントをそのバージョン以上に更新するか、新しいバージョンでファイルが開かれる前に作成したバックアップを復元してから、再度読み込みます。

コンテナがループして再起動します。 docker compose logs actual を確認します。/data の権限エラーは、マウントされたディレクトリにコンテナのユーザーが書き込めないことを示します。アドレス使用中のエラーは、loopback 上の 5006 をすでに別のプロセスが使用していることを示します。

初回の読み込みが遅く感じられます。 開くときに予算ファイル全体がブラウザーへダウンロードされます。大きな転送が 1 回行われ、その後はローカルから読み取ります。これはサーバーのサイズ設定の問題ではないため、RAM を追加しても変わりません。

FAQ

Actual Budget の動作に HTTPS は必要ですか?

はい。実際には必要です。Actual はブラウザーの SharedArrayBuffer 機能を必要とし、ブラウザーは安全なコンテキスト、つまり https:// または http://localhost でのみこの機能を有効にします。別のマシンから通常の HTTP でアクセスすると、アプリは読み込まれません。公式 iOS アプリはベータテスト中で、公式 Android アプリはまだないため、多くのスマートフォンでは同じ Web アプリを使用し、同じ制約を受けます。(FAQ) 実在するホスト名には Let's Encrypt 証明書を使用してください。各デバイスで証明書警告が表示されても許容できる場合は、ACTUAL_HTTPS_KEY と ACTUAL_HTTPS_CERT を使った自己署名証明書を使用できます。

Actual では銀行取引を自動的にインポートできますか?

自分で登録したサードパーティーサービスを介する場合に限り可能です。北米では SimpleFIN Bridge、欧州では Enable Banking、ニュージーランドでは Akahu、ブラジルでは Pluggy.ai を利用できます。GoCardless もサポートされていますが、新規アカウントは受け付けていません。これらの API 認証情報はサーバー上に保存され、エンドツーエンド暗号化の対象外です。自動同期はデフォルトで無効になっており、有効にしても Actual が開いている間だけ実行されるため、サーバー上でバックグラウンドのポーリングは行われません。CSV、QIF、OFX、QFX、CAMT のインポートには、サードパーティーサービスは一切必要ありません。

具体的に何をバックアップする必要がありますか?

このガイドで /opt/actual/data としてマウントしているデータディレクトリです。そこにはログイン情報とセッションを含む server-files/account.sqlite と、予算ファイルを含む user-files があります。実行中の SQLite データベースをコピーすると書き込みの一部だけが取得される可能性があるため、コピーする前にコンテナを停止してください。サーバー上で状態を保持するものは、ほかにはありません。

暗号化パスワードを失うとどうなりますか?

ファイルを復元できなくなります。エンドツーエンド暗号化の目的どおり、パスワードがサーバーに届くことはないため、リセットもサポートによる復旧手段もありません。ファイルを作成した時点でパスワードマネージャーに保存し、このサーバー自体に依存しない場所にもコピーを保管してください。

Actual Budget にはどの程度のサーバーが必要ですか?

ごくわずかです。コンテナは静的アセットとファイルを提供し、予算の計算はブラウザーで行われます。共有 vCPU 1 個と 1 GB の RAM があれば問題なく動作し、数年分の履歴がある家庭用予算のデータディレクトリも数十 MB に収まります。ディスク容量を圧迫するのはバックアップとほかのコンテナであり、Actual ではありません。これと同時に、より多くのリソースを必要とするものも実行するサーバーを選ぶ場合は、通常、必要な最低スペックを決めるのはフォトサーバーです。そのため、プランを選ぶ前に PhotoPrism と Immich に実際に必要な RAM を確認してください。同じ考え方はメディアスタックにも当てはまります。プランを決めるのはトランスコードであり、Jellyfin のライブラリを歩き回れる 90 年代のビデオストアに変える Halcyon のようなブラウザーフロントエンドは、Actual と同程度のリソースしか必要としません。

この記事は役に立ちましたか?