SSD Nodes Learn 8GB RAM — 연 $66
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-01

VPS에 Actual Budget 자체 호스팅하기

Docker Compose로 Actual Budget을 VPS에 설치하는 방법을 설명합니다. 데이터 볼륨, 브라우저에 HTTPS가 필요한 이유, 첫 예산 파일, 은행 가져오기와 백업까지 다룹니다.

구축할 내용

Actual Budget은 자체 호스팅할 수 있는 봉투 예산 관리 앱입니다. 직접 호스팅할 수 있는 YNAB 대안을 찾을 때 일반적으로 선택하는 앱입니다. 서버는 컨테이너 1개, 데이터 볼륨 1개, HTTPS 이름 1개로 구성됩니다. 일반적인 예산 관리에 필요한 모든 기능은 임대할 수 있는 가장 작은 VPS에서도 충분히 실행됩니다. 서버가 주로 파일을 저장하고 동기화하기 때문입니다.

명령을 입력하기 전에 아키텍처를 이해해야 합니다. 예산 자체는 브라우저와 각 모바일 앱 내부에 저장되는 SQLite 데이터베이스입니다. 지금 설치하려는 서버는 동기화 엔드포인트입니다. 서버는 계정 목록, 예산 파일, 그리고 휴대폰과 노트북이 동일한 상태를 유지하도록 하는 변경 로그를 보관합니다. 따라서 서버가 중단되어도 앱은 계속 작동합니다. 또한 클라이언트 1개에 복사본이 남아 있는 한 서버를 잃어도 예산을 잃지 않습니다.

서버에 HTTPS가 필요한 이유

Actual은 HTTPS를 요구합니다. 이는 형식적인 요구 사항이 아닙니다. 브라우저는 사양에서 보안 컨텍스트라고 부르는 환경에서만 Web Crypto API를 노출합니다. Web Crypto API는 Actual이 종단 간 암호화에 사용하는 인터페이스입니다. 보안 컨텍스트는 https:// 또는 http://localhost입니다. 다른 컴퓨터의 브라우저에서 http://203.0.113.10:5006으로 앱을 열면 이러한 기능을 사용할 수 없습니다. 브라우저가 해당 기능을 페이지에 전달하지 않기 때문입니다. 공식 모바일 빌드도 일반 http:// 서버 URL을 거부합니다.

따라서 사용할 수 있는 구성은 2가지입니다. 컨테이너 앞에 실제 이름에 대한 유효한 인증서를 배치하는 방법입니다. 이 가이드에서는 이 방법을 사용합니다. 또는 프로젝트 문서에 설명된 대로 ACTUAL_HTTPS_KEYACTUAL_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이 있으며, 여기에 로그인 정보와 세션 토큰이 포함된 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에서 수신 대기 중이라고 보고하면 로그가 안정됩니다. 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 줄로 확인할 수 있습니다.

인증서와 실제 이름 설정

A 레코드가 VPS, budget.example.com을 가리키도록 설정하고 DNS가 반영될 때까지 기다립니다. 그런 다음 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 access log에는 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에서 종단 간 암호화를 활성화할지 묻습니다. 암호화를 활성화합니다. 그러면 서버에는 암호문만 저장되므로, 임대한 머신에서 금융 데이터를 처리할 때 적절한 선택입니다. 다만 실제 비용이 따릅니다. 암호화 비밀번호는 서버로 전송되지 않으므로, 비밀번호를 잃어버리면 파일을 복구할 수 없고 재설정 방법도 없습니다. 해당 화면에서 다음 단계로 이동하기 전에 비밀번호를 기록합니다.

수년간의 거래 내역을 가져오는 대신 은행의 현재 잔액을 기준으로 시작 잔액을 설정합니다. 봉투식 예산 방식은 현재 보유한 금액을 기준으로 앞으로 진행되므로, 과거 내역이 없어도 문제가 되지 않습니다.

거래 내역 가져오기

이 기능에서는 열정보다 정직함이 중요합니다. 가져오기 기능이 self-hosted budgeting을 사용하지 않게 되는 가장 큰 이유이기 때문입니다.

수동 입력은 기본이며 항상 작동합니다. envelope method에서는 수동 입력이 핵심이라고도 할 수 있습니다. 구매 내역을 직접 입력해야 지출을 인식하게 되기 때문입니다.

파일 가져오기는 대부분의 거래 내역을 처리합니다. Actual은 CSV, QIF, OFX 및 QFX를 실제로 읽으며, 모든 은행은 이 형식 중 하나 이상으로 내보냅니다. account 화면에서 account별로 가져오기를 실행하고, 열 매핑을 한 번 설정하면 Actual이 해당 account의 레이아웃을 기억합니다.

은행 자동 동기화도 지원하지만, server가 자체적으로 은행과 통신할 수 없으므로 third-party service가 필요합니다. Actual은 북미 은행에 SimpleFIN Bridge, 유럽에 Enable Banking, 뉴질랜드에 Akahu, 브라질에 Pluggy.ai를 지원합니다. GoCardless도 계속 지원되지만 신규 account는 받지 않습니다. 사용자가 provider에 직접 가입하고, credentials를 생성한 다음, 이를 server에 추가해야 합니다. SimpleFIN Bridge의 요금은 2026년 7월 기준으로 최대 25개 기관에 대해 연간 15 US dollars이며, 다른 provider의 요금은 서로 다릅니다.

이 기능에 의존하기 전에 두 가지 제한을 받아들여야 합니다. API credentials는 server에 저장되며 end-to-end encryption의 보호를 받지 않습니다. server가 credentials를 사용해야 하기 때문입니다. 또한 Actual은 자동으로 조회하지 않습니다. 동기화는 사용자가 누르는 버튼이며 background job이 아닙니다.

백업은 파일만 백업하면 되기 때문입니다

관리해야 하는 모든 데이터는 /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 자체의 클라이언트 측 백업은 별도의 기능이며 알아 둘 가치가 있습니다. 브라우저는 파일 메뉴에서 접근할 수 있는 예산 파일의 최근 복사본을 보관합니다. 따라서 서버에 전혀 접근하지 않고도 "실수로 카테고리를 삭제했습니다"와 같은 문제를 해결할 수 있습니다.

서버 업데이트

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

Compose는 새 이미지로 컨테이너를 다시 만들고 동일한 볼륨을 다시 연결하므로 데이터가 유지됩니다. 클라이언트도 업데이트합니다. 서버 버전과 앱 버전은 서로 비슷한 수준을 유지해야 합니다. 서버보다 훨씬 오래된 클라이언트는 버전 불일치 메시지를 표시하며 동기화를 거부할 수 있습니다. 주요 버전으로 업그레이드하기 전에 백업을 수행합니다. 최초 시작 시 마이그레이션이 실행되며 다운그레이드 경로가 없기 때문입니다.

문제가 발생하는 지점과 확인할 내용

앱은 로드되지만 동기화가 완료되지 않습니다. nginx access log에서 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_KEYACTUAL_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개와 RAM 1 GB를 사용하면 문제없이 실행됩니다. 수년간의 기록이 있는 가정용 예산의 데이터 디렉터리도 수십 MB 수준으로 유지됩니다. 디스크 공간을 압박하는 원인은 Actual이 아니라 백업과 다른 컨테이너입니다.