Actual Budget 셀프 호스팅: Docker Compose 구축 가이드
Docker Compose를 사용하여 VPS에 Actual Budget을 설치하는 방법을 상세히 안내합니다. HTTPS 설정이 필수적인 이유와 데이터 동기화 원리, 그리고 안정적인 예산 관리를 위한 백업 및 데이터 볼륨 구성 전략을 확인하십시오.
구축할 내용
Actual Budget은 셀프 호스팅이 가능한 봉투 예산 관리 앱이며, 직접 호스팅할 수 있는 YNAB 대안을 찾는 사용자들에게 주로 추천되는 솔루션입니다. 서버는 컨테이너 1개, 데이터 볼륨 1개, HTTPS 도메인 1개로 구성됩니다. 서버는 주로 파일을 저장하고 동기화하는 역할을 수행하므로, 일반적인 예산 관리 기능은 가장 저렴한 VPS에서도 원활하게 작동합니다.
명령어를 입력하기 전에 아키텍처를 이해하는 것이 중요합니다. 예산 데이터 자체는 브라우저와 각 모바일 앱 내부에 저장되는 SQLite 데이터베이스입니다. 지금 설치하려는 서버는 동기화 엔드포인트 역할을 하며, 계정 목록, 예산 파일, 그리고 휴대폰과 노트북 간의 데이터 일치를 위한 변경 로그를 보관합니다. 이러한 구조 덕분에 서버가 다운되어도 앱은 계속 작동하며, 클라이언트 중 하나라도 복사본을 가지고 있다면 서버를 잃어버려도 예산 데이터는 손실되지 않습니다.
서버에 HTTPS가 필요한 이유
Actual은 HTTPS를 요구하며, 이는 단순한 형식이 아닙니다. 브라우저는 Actual의 종단간 암호화에 사용하는 인터페이스인 Web Crypto API를 사양에서 정의하는 보안 컨텍스트(secure context)에서만 노출합니다. 보안 컨텍스트란 https:// 또는 http://localhost를 의미합니다. 다른 기기의 브라우저에서 http://203.0.113.10:5006을 통해 앱을 로드하면 브라우저가 해당 기능을 페이지에 제공하지 않으므로 기능을 사용할 수 없습니다. 공식 모바일 빌드 또한 일반 http:// 서버 URL 연결을 거부합니다.
따라서 두 가지 실행 가능한 설정 방법이 있습니다. 이 가이드에서 다루는 방식처럼 컨테이너 앞에 실제 도메인과 유효한 인증서를 배치하는 방법이 있습니다. 또는 프로젝트 문서에 설명된 대로 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이 파일에서 중요한 세부 사항은 세 가지입니다.
이미지는 actualbudget/actual-server:latest이며, 프로젝트 측에서 Docker Hub에 배포하고 ghcr.io/actualbudget/actual에 미러링합니다. 저전력 기기를 위해 latest-alpine 태그가 제공됩니다.
컨테이너는 모든 데이터를 /data 아래에 기록합니다. 컨테이너 내부에는 로그인 및 세션 토큰을 저장하는 server-files와 실제 가계부 파일이 담긴 account.sqlite, user-files가 있습니다. 해당 경로를 마운트하지 않으면 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은 컨테이너가 실행 중이지 않다는 뜻이며, docker compose ps을 통해 종료된 이유를 확인할 수 있습니다. 가장 흔한 원인은 마운트된 볼륨의 권한 문제이며, 로그에서 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를 엽니다. 첫 화면에서 서버 비밀번호를 설정하라는 메시지가 나타납니다. 이 단일 비밀번호가 서버 전체를 보호하므로, 길고 무작위적인 비밀번호를 생성하여 자체 호스팅 Vaultwarden 비밀번호 관리자와 같이 안전한 곳에 보관하십시오. 별도의 사용자 계정을 생성할 필요는 없습니다. Actual 서버는 설계상 단일 비밀번호 체계를 사용하므로, 예산을 공유하려면 해당 비밀번호를 공유해야 합니다.
그다음 예산 파일을 생성합니다. Actual은 종단간 암호화(end-to-end encryption)를 활성화할지 묻습니다. '예'를 선택하십시오. 서버는 암호문만 저장하게 되며, 이는 임대 서버에서 금융 데이터를 다룰 때 올바른 선택입니다. 단, 대가가 따릅니다. 암호화 비밀번호는 서버로 전송되지 않으므로, 비밀번호를 분실하면 파일을 복구할 방법이 없으며 재설정도 불가능합니다. 해당 화면을 넘기기 전에 반드시 비밀번호를 기록해 두십시오.
수년 치의 내역을 가져오기보다는 은행의 현재 잔액을 기준으로 시작 잔액을 설정하십시오. 봉투 예산법(Envelope budgeting)은 현재 보유한 자금을 바탕으로 미래를 계획하는 방식이므로, 과거 내역이 없어도 운영에 지장이 없습니다.
거래 내역 가져오기
이 단계에서는 열정보다 정직함이 중요합니다. 가져오기 과정이 사용자가 직접 호스팅하는 가계부 서비스에서 이탈하는 주된 이유이기 때문입니다.
수동 입력은 가장 기본적인 방법이며 언제나 확실하게 작동합니다. 봉투 예산법을 사용한다면 직접 구매 내역을 입력하는 과정 자체가 지출을 인지하게 만드는 핵심이므로, 수동 입력이 오히려 권장됩니다.
파일 가져오기는 대량의 데이터를 처리할 때 유용합니다. Actual은 CSV, QIF, OFX, QFX 형식을 읽을 수 있으며, 모든 은행은 최소한 이 중 하나 이상의 형식을 내보내기 지원합니다. 계정 화면에서 계정별로 가져오기를 수행하고 열을 한 번 매핑해 두면, Actual은 해당 계정의 레이아웃을 기억합니다.
자동 은행 동기화 기능도 존재하지만, 서버가 직접 은행과 통신할 수 없으므로 타사 서비스가 필요합니다. Actual은 북미 지역 은행을 위한 SimpleFIN Bridge, 유럽을 위한 Enable Banking, 뉴질랜드의 Akahu, 브라질의 Pluggy.ai를 지원합니다. GoCardless도 여전히 지원되지만 신규 계정은 받지 않습니다. 사용자가 직접 해당 제공업체에 가입하여 자격 증명을 생성한 뒤 서버에 추가해야 합니다. 2026년 7월 기준으로 SimpleFIN Bridge는 최대 25개 기관에 대해 연간 15 US 달러의 요금을 부과하며, 다른 서비스들은 각기 다른 가격 정책을 따릅니다.
이 기능을 사용하기 전에 다음 두 가지 제한 사항을 인지해야 합니다. 첫째, API 자격 증명은 서버에 저장되며 서버가 이를 사용해야 하므로 종단 간 암호화(end-to-end encryption)가 적용되지 않습니다. 둘째, 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는 새 이미지로 컨테이너를 다시 만들고 동일한 volume을 다시 연결하므로 데이터가 유지된다. 클라이언트도 업데이트한다. 서버 버전과 애플리케이션 버전은 서로 크게 차이 나지 않게 유지해야 한다. 서버보다 훨씬 오래된 클라이언트는 버전 불일치 메시지를 표시하고 동기화를 거부할 수 있다. 주 버전을 크게 올리기 전에는 백업을 수행한다. 첫 시작 시 migration이 실행되며 downgrade 경로가 없기 때문이다. 상태가 파일 디렉터리로 구성된 Actual은 floating latest tag를 사용해도 문제가 적다. 반면 실제 database를 사용하는 애플리케이션은 그렇지 않다. Chatwoot self-hosting에서는 고정된 tag와 업그레이드 전 dump를 사용하는 이 작업 방식을 설명한다.
발생하는 문제와 확인 방법
애플리케이션은 로드되지만 동기화가 완료되지 않습니다. nginx 접근 로그에서 413을 확인하십시오. 이는 client_max_body_size가 너무 낮게 설정되었기 때문입니다. 반면 502이 나타난다면 nginx는 정상 작동 중이나 컨테이너가 실행되지 않은 상태입니다.
암호화 옵션이 없거나 모바일 앱에서 URL 접속을 거부합니다. 페이지가 보안 컨텍스트에서 실행되지 않고 있습니다. 주소창에 IP 주소나 localhost가 아닌 호스트 이름으로 http://이 표시될 것입니다. 우회 방법을 찾기보다 인증서 문제를 해결하십시오.
예산 파일이 현재 버전과 호환되지 않는다는 메시지가 나타납니다. 클라이언트와 서버 버전이 일치하지 않는 상태입니다. 양쪽 모두 동일한 릴리스로 업데이트한 뒤 다시 로드하십시오.
컨테이너가 반복적으로 재시작됩니다. docker compose logs actual을 확인하십시오. /data에서 권한 오류가 발생한다면 마운트된 디렉터리에 컨테이너 사용자의 쓰기 권한이 없는 것입니다. 주소 사용 중(address-in-use) 오류는 다른 프로세스가 이미 loopback의 5006 포트를 점유하고 있음을 의미합니다.
첫 로딩이 느리게 느껴집니다. 예산을 열 때 전체 파일이 브라우저로 다운로드됩니다. 이는 한 번의 큰 데이터 전송 후 로컬에서 읽기 작업을 수행하기 때문입니다. 서버 사양 문제나 RAM 추가로 해결할 수 있는 부분이 아닙니다.
FAQ
Actual Budget이 작동하려면 HTTPS가 필수입니까?
네, 실질적으로 필수입니다. Actual의 종단간 암호화(end-to-end encryption)는 브라우저의 Web Crypto 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을 운영하려면 어느 정도의 서버 사양이 필요합니까?
매우 낮은 사양으로도 충분합니다. 컨테이너는 정적 에셋과 파일을 제공할 뿐이며, 예산 계산은 브라우저에서 수행됩니다. 1개의 공유 vCPU와 1 GB RAM만으로도 원활하게 작동하며, 수년간의 기록이 담긴 가계부 데이터 디렉터리도 수십 MB 수준에 불과합니다. 디스크 부하는 Actual이 아닌 백업이나 다른 컨테이너에서 발생합니다. 더 높은 사양을 요구하는 서비스와 함께 운영할 계획이라면, 보통 사진 서버가 최소 사양을 결정하게 됩니다. 따라서 플랜을 선택하기 전에 PhotoPrism과 Immich가 실제로 필요로 하는 RAM 용량을 먼저 확인하십시오. 미디어 스택도 같은 논리가 적용됩니다. 트랜스코딩 작업이 플랜을 결정하며, Jellyfin 라이브러리를 90년대 비디오 대여점처럼 탐색하게 해주는 Halcyon 같은 브라우저 프론트엔드는 Actual과 비슷하게 매우 적은 자원을 소모합니다.