SSD Nodes Learn 8GB RAM — $66/năm
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-01

Hướng dẫn tự cài đặt Actual Budget trên VPS bằng Docker

Triển khai Actual Budget qua Docker Compose trên VPS. Bài viết hướng dẫn cấu hình data volume, thiết lập HTTPS bắt buộc, import dữ liệu ngân sách và quy trình backup an toàn.

Bạn đang xây dựng cái gì

Actual Budget là một ứng dụng quản lý ngân sách theo phương pháp phong bì (envelope budgeting) có thể tự lưu trữ (self-hosted). Đây là giải pháp thường được đề xuất khi người dùng tìm kiếm một phương án thay thế YNAB mà họ có thể tự vận hành. Server này bao gồm một container, một data volume và một tên miền HTTPS. Mọi nhu cầu quản lý ngân sách thông thường đều chạy ổn định trên VPS nhỏ nhất mà bạn có thể thuê, vì server chủ yếu thực hiện lưu trữ file và đồng bộ hóa chúng.

Kiến trúc này rất đáng để tìm hiểu trước khi bạn bắt đầu nhập bất kỳ lệnh nào. Bản thân ngân sách là một database SQLite nằm bên trong trình duyệt và bên trong mỗi ứng dụng di động. Server mà bạn sắp cài đặt đóng vai trò là một sync endpoint: nó lưu trữ danh sách tài khoản, các file ngân sách và nhật ký thay đổi (change log) giúp điện thoại và laptop đồng bộ dữ liệu với nhau. Đó là lý do tại sao ứng dụng vẫn hoạt động khi server ngoại tuyến, và cũng là lý do tại sao việc mất server không làm mất ngân sách của bạn, miễn là một client vẫn còn giữ bản sao dữ liệu.

Tại sao server cần HTTPS

Actual yêu cầu HTTPS, đây không phải là thủ tục hình thức. Các trình duyệt chỉ cung cấp Web Crypto API, giao diện mà Actual sử dụng để mã hóa đầu cuối, trong cái mà đặc tả gọi là ngữ cảnh bảo mật. Ngữ cảnh bảo mật là https:// hoặc http://localhost. Nếu bạn tải ứng dụng từ http://203.0.113.10:5006 trên trình duyệt của máy khác, các tính năng đó sẽ không xuất hiện vì trình duyệt không cung cấp chúng cho trang web. Các bản build di động chính thức cũng từ chối URL server dạng http:// thuần túy.

Vì vậy, có hai thiết lập khả thi. Đặt một chứng chỉ thực trên một tên miền thực phía trước container, đây là cách hướng dẫn này thực hiện. Hoặc cấp cho server một chứng chỉ tự ký với ACTUAL_HTTPS_KEYACTUAL_HTTPS_CERT, như tài liệu dự án đã nêu, và chấp nhận cảnh báo của trình duyệt trên mọi thiết bị. Một chứng chỉ miễn phí từ Let's Encrypt chỉ mất năm phút để thiết lập, vì vậy hãy chọn phương án đầu tiên.

Cài đặt Actual Budget với Docker Compose

Hãy cài đặt Docker trước nếu VPS của bạn mới khởi tạo. Nếu cú pháp file Compose còn mới lạ với bạn, hướng dẫn các kiến thức cơ bản về Docker Compose cho VPS sẽ giải thích các trường được sử dụng dưới đây.

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

Tạo file /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

Có ba chi tiết trong file này cần lưu ý.

Image được sử dụng là actualbudget/actual-server:latest, do dự án phát hành lên Docker Hub và được mirror tại ghcr.io/actualbudget/actual. Có một tag latest-alpine dành cho các máy có cấu hình thấp.

Container ghi mọi dữ liệu vào đường dẫn /data. Bên trong đó, bạn sẽ thấy server-files, nơi lưu trữ account.sqlite chứa thông tin đăng nhập và session token của bạn, cùng với user-files, nơi chứa chính các file ngân sách. Hãy mount đường dẫn này, nếu không docker compose pull sẽ làm mất dữ liệu ngân sách của bạn. Bạn có thể dùng ACTUAL_DATA_DIR để di chuyển nó, nhưng giá trị mặc định là đủ dùng.

Cổng được publish chỉ trên 127.0.0.1. Nếu chỉ dùng 5006:5006, nó sẽ publish trên mọi interface, và Docker sẽ tự ghi đè các quy tắc của nó lên trước ufw, khiến ứng dụng bị lộ ra internet ngay cả khi bạn đã thiết lập firewall chặn tất cả. Sự cố này được giải thích trong bài tại sao các cổng được publish bởi Docker lại bỏ qua ufw. Việc bind vào loopback đảm bảo chỉ có reverse proxy trên cùng máy chủ mới có thể truy cập được nó.

Khởi chạy ứng dụng:

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

Log sẽ ổn định khi server báo rằng nó đang lắng nghe trên cổng 5006. Hãy kiểm tra cục bộ trước khi bạn cấu hình DNS:

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

200 nghĩa là ứng dụng đang hoạt động. curl: (7) Failed to connect nghĩa là container không chạy, và docker compose ps sẽ cho thấy nó đã thoát. Nguyên nhân thường gặp là vấn đề phân quyền trên volume được mount, hiển thị dưới dạng dòng EACCES trong log.

Cấu hình chứng chỉ và tên miền

Trỏ bản ghi A về VPS, budget.example.com, và đợi cho đến khi phân giải thành công. Sau đó, cài đặt nginx và cấp chứng chỉ. Hướng dẫn Certbot trên Ubuntu 24.04 với nginx bao gồm đầy đủ quy trình cấp chứng chỉ và bộ đếm thời gian gia hạn.

Khối 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 là dòng lệnh mà mọi người thường quên. File ngân sách được tải lên toàn bộ trong một lần đồng bộ đầy đủ. Nginx mặc định giới hạn body của request là 1 MB, vì vậy khi file vượt quá kích thước này, quá trình đồng bộ sẽ thất bại với lỗi 413 Request Entity Too Large trong log truy cập của nginx, trong khi ứng dụng chỉ hiển thị lỗi đồng bộ chung chung. Bản thân server cũng có các giới hạn riêng: ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB mặc định là 20 và ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB mặc định là 50, vì vậy hãy đặt giới hạn của nginx cao hơn giá trị nào áp dụng cho trường hợp của bạn.

Tải lại và kiểm tra:

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

Lần chạy đầu tiên: mật khẩu và tệp ngân sách đầu tiên của bạn

Mở https://budget.example.com trong trình duyệt. Màn hình đầu tiên yêu cầu bạn đặt mật khẩu máy chủ. Mật khẩu duy nhất này bảo vệ toàn bộ máy chủ, vì vậy hãy tạo một mật khẩu dài, ngẫu nhiên và lưu lại ở nơi bạn có thể tìm thấy, ví dụ như trong trình quản lý mật khẩu Vaultwarden tự lưu trữ. Không có tài khoản người dùng nào cần tạo. Máy chủ của Actual được thiết kế chỉ dùng một mật khẩu, vì vậy việc chia sẻ ngân sách đồng nghĩa với việc chia sẻ mật khẩu đó.

Sau đó, hãy tạo một tệp ngân sách. Actual sẽ hỏi bạn có muốn bật mã hóa đầu cuối hay không. Hãy chọn có, khi đó máy chủ chỉ lưu trữ văn bản mã hóa (ciphertext), đây là lựa chọn đúng đắn cho dữ liệu tài chính trên một máy chủ thuê ngoài. Cái giá phải trả là thực tế: mật khẩu mã hóa không bao giờ được gửi đến máy chủ, vì vậy nếu bạn làm mất nó, tệp tin sẽ mất vĩnh viễn và không có cách nào để khôi phục. Hãy ghi lại mật khẩu trước khi nhấn tiếp tục qua màn hình đó.

Hãy thiết lập số dư bắt đầu dựa trên số dư hiện tại từ ngân hàng của bạn thay vì nhập dữ liệu lịch sử nhiều năm. Phương pháp lập ngân sách theo phong bì (envelope budgeting) hoạt động dựa trên số tiền bạn đang có, vì vậy việc không có lịch sử giao dịch sẽ không gây ảnh hưởng gì.

Nhập giao dịch

Đây là lúc sự trung thực quan trọng hơn sự nhiệt tình, vì quy trình nhập liệu là lý do chính khiến người dùng từ bỏ việc tự lưu trữ (self-hosted) ứng dụng quản lý ngân sách.

Nhập thủ công là phương thức cơ bản và luôn hoạt động. Đối với phương pháp phong bì, đây có thể coi là mục đích chính, vì việc tự tay nhập một khoản chi tiêu giúp bạn nhận thức rõ hơn về nó.

Nhập tệp tin xử lý khối lượng dữ liệu lớn. Actual đọc được các định dạng CSV, QIF, OFX và QFX, và mọi ngân hàng đều xuất dữ liệu ra ít nhất một trong các định dạng này. Hãy thực hiện nhập cho từng tài khoản từ màn hình tài khoản, ánh xạ các cột một lần, và Actual sẽ ghi nhớ bố cục đó cho tài khoản đó.

Đồng bộ ngân hàng tự động có tồn tại, và nó cần một dịch vụ bên thứ ba vì server không thể tự giao tiếp với các ngân hàng. Actual hỗ trợ SimpleFIN Bridge cho các ngân hàng Bắc Mỹ, Enable Banking cho châu Âu, Akahu cho New Zealand và Pluggy.ai cho Brazil. GoCardless vẫn được hỗ trợ nhưng không chấp nhận tài khoản mới. Bạn tự đăng ký với nhà cung cấp, tạo thông tin xác thực và thêm chúng vào server. Tính đến tháng 7 năm 2026, SimpleFIN Bridge tính phí 15 đô la Mỹ mỗi năm cho tối đa 25 tổ chức, và các dịch vụ khác có mức giá khác nhau.

Có hai giới hạn cần chấp nhận trước khi bạn dựa vào tính năng này. Thông tin xác thực API nằm trên server và không được bảo vệ bởi mã hóa đầu cuối (end-to-end encryption), vì server phải sử dụng chúng. Và Actual không tự động truy vấn: đồng bộ hóa là một nút bạn nhấn, không phải là một tác vụ chạy ngầm.

Sao lưu, vì đó chỉ là các tập tin

Mọi dữ liệu bạn quan tâm đều nằm trong /opt/actual/data. Không có bước xuất dữ liệu (export) hay cần script để dump cơ sở dữ liệu.

Cạm bẫy duy nhất là SQLite. Việc sao chép account.sqlite trong khi server đang ghi dữ liệu có thể dẫn đến việc lấy phải giao dịch chưa hoàn tất, và bạn sẽ không biết điều đó cho đến khi thử khôi phục. Hãy dừng container trong vài giây khi thực hiện sao chép:

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

Hãy đưa lệnh này vào lịch trình với phương pháp trong sao lưu restic trên VPS, nội dung này bao gồm việc thiết lập kho lưu trữ, chính sách lưu giữ và quy trình khôi phục. Hãy thực hiện diễn tập khôi phục. Một bản sao lưu chưa từng được khôi phục chỉ là một sự phỏng đoán.

Các bản sao lưu phía client của Actual là một tính năng riêng biệt và rất đáng lưu tâm. Trình duyệt lưu giữ các bản sao gần đây của tập tin ngân sách, có thể truy cập từ menu tập tin, giúp giải quyết tình huống "tôi lỡ tay xóa nhầm một danh mục" mà không cần can thiệp vào server.

Cập nhật server

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

Compose tạo lại container từ image mới và gắn lại cùng volume đó, vì vậy dữ liệu vẫn được giữ nguyên. Hãy cập nhật cả các client. Các phiên bản server và ứng dụng cần phải tương đồng với nhau, và một client quá cũ so với server có thể từ chối đồng bộ kèm thông báo lỗi lệch phiên bản. Hãy thực hiện backup trước khi nâng cấp phiên bản lớn, vì các tiến trình migration sẽ chạy ngay lần khởi động đầu tiên và không có đường lùi cho việc hạ cấp.

Những lỗi thường gặp và cách nhận biết

Ứng dụng tải được nhưng quá trình đồng bộ không bao giờ hoàn tất. Hãy kiểm tra log truy cập của nginx để tìm 413. Điều này có nghĩa là client_max_body_size đang được đặt quá thấp. Một 502 xuất hiện đồng nghĩa với việc nginx đang chạy nhưng container thì không.

Các tùy chọn mã hóa bị thiếu, hoặc ứng dụng di động từ chối URL. Trang web không nằm trong ngữ cảnh bảo mật. Thanh địa chỉ sẽ hiển thị http:// kèm theo địa chỉ IP hoặc hostname không phải là localhost. Hãy sửa chứng chỉ thay vì tìm cách vượt qua nó.

Thông báo tệp ngân sách không tương thích với phiên bản này. Phiên bản của client và server đã bị lệch nhau. Hãy cập nhật cả hai lên cùng một bản release và tải lại.

Container khởi động lại liên tục. Hãy đọc docker compose logs actual. Lỗi phân quyền trên /data có nghĩa là thư mục được mount không cho phép người dùng của container ghi dữ liệu. Lỗi address-in-use có nghĩa là một tiến trình khác đã chiếm cổng 5006 trên loopback.

Lần tải đầu tiên có cảm giác chậm. Toàn bộ tệp ngân sách sẽ tải về trình duyệt khi bạn mở nó. Đây là một lần truyền dữ liệu lớn, sau đó là các thao tác đọc cục bộ. Đây không phải là vấn đề về cấu hình server, và việc thêm RAM sẽ không cải thiện được tình trạng này.

FAQ

Actual Budget có cần HTTPS để hoạt động không?

Có, trên thực tế là cần. Tính năng mã hóa đầu cuối của Actual sử dụng Web Crypto API của trình duyệt, và các trình duyệt chỉ cung cấp tính năng này trong ngữ cảnh bảo mật, nghĩa là https:// hoặc http://localhost. Qua HTTP thông thường từ một máy khác, các tính năng này sẽ không khả dụng, và các ứng dụng di động chính thức sẽ từ chối URL máy chủ HTTP thông thường. Hãy sử dụng chứng chỉ Let's Encrypt trên một hostname thực, hoặc chứng chỉ tự ký với ACTUAL_HTTPS_KEYACTUAL_HTTPS_CERT nếu bạn chỉ sử dụng trình duyệt trên máy tính để bàn.

Actual có thể tự động nhập giao dịch ngân hàng của tôi không?

Chỉ thông qua dịch vụ bên thứ ba mà bạn tự đăng ký: SimpleFIN Bridge ở Bắc Mỹ, Enable Banking ở Châu Âu, Akahu ở New Zealand, hoặc Pluggy.ai ở Brazil. GoCardless được hỗ trợ nhưng hiện không chấp nhận tài khoản mới. Các thông tin xác thực API đó nằm trên máy chủ của bạn và không được bảo vệ bởi mã hóa đầu cuối. Việc đồng bộ cũng là thủ công, vì vậy bạn phải nhấn nút và không có tiến trình nào chạy ngầm. Việc nhập file CSV, QIF, OFX và QFX không cần bất kỳ bên thứ ba nào.

Tôi cần sao lưu chính xác những gì?

Thư mục dữ liệu đã mount, đó là /opt/actual/data trong hướng dẫn này. Nó chứa server-files/account.sqlite với thông tin đăng nhập và phiên làm việc, và user-files với các file ngân sách. Hãy dừng container trước khi sao chép, vì sao chép một cơ sở dữ liệu SQLite đang hoạt động có thể dẫn đến việc ghi dữ liệu không hoàn chỉnh. Không có thành phần nào khác trên máy chủ lưu trữ trạng thái.

Điều gì xảy ra nếu tôi quên mật khẩu mã hóa?

File đó không thể khôi phục được. Mật khẩu không bao giờ được gửi đến máy chủ, đó chính là mục đích của mã hóa đầu cuối, vì vậy không có cách nào để đặt lại và không có hỗ trợ kỹ thuật. Hãy lưu nó vào trình quản lý mật khẩu ngay khi bạn tạo file, và giữ một bản sao ở nơi không phụ thuộc vào chính máy chủ này.

Actual Budget cần cấu hình máy chủ như thế nào?

Rất ít. Container chỉ phục vụ các tài nguyên tĩnh và file, còn các phép tính ngân sách diễn ra ngay trên trình duyệt. Một vCPU dùng chung với 1 GB RAM là đủ để chạy mà không gặp vấn đề gì, và thư mục dữ liệu cho một ngân sách gia đình với lịch sử vài năm cũng chỉ chiếm vài chục megabyte. Áp lực lên ổ đĩa đến từ các bản sao lưu và các container khác của bạn, chứ không phải từ Actual.