Self-hosted personal finance sync server (Actual Budget) on FreeBSD.
| Registry | ghcr.io/daemonless/actual |
| Source | https://github.com/actualbudget/actual |
| Website | https://actualbudget.org/ |
| Tag | Description | Best For |
|---|---|---|
latest |
Upstream Binary. Built from official release. | Most users — recommended. |
Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions.
services:
actual:
image: "ghcr.io/daemonless/actual:latest"
container_name: actual
environment:
- PUID=1000 # User ID for the application process
- PGID=1000 # Group ID for the application process
- TZ=Etc/UTC # Timezone for the container
- NODE_ENV=production # Node runtime mode; leave as 'production'
- ACTUAL_PORT=5006 # Internal port the sync server listens on; leave as 5006
- ACTUAL_HOSTNAME=0.0.0.0 # Bind address for the sync server; leave as 0.0.0.0 (the app's own default, '::', is IPv6-any and breaks single-stack containers)
- ACTUAL_DATA_DIR=/data # Directory where Actual stores its account database, budget files and server config (under the /data volume)
volumes:
- "/containers/actual/data:/data"
# always (not unless-stopped) so FreeBSD's podman rc.d auto-starts it at boot
restart: alwaysSave as compose.yaml, then run podman-compose up -d.
.env:
# .env
DIRECTOR_PROJECT=actual
PUID=1000
PGID=1000
TZ=Etc/UTC
NODE_ENV=production
ACTUAL_PORT=5006
ACTUAL_HOSTNAME=0.0.0.0
ACTUAL_DATA_DIR=/data
appjail-director.yml:
# appjail-director.yml
options:
- virtualnet: ':<random> default'
- nat:
services:
actual:
name: actual
options:
- container: 'args:--pull'
oci:
user: root
environment:
- PUID: !ENV '${PUID}'
- PGID: !ENV '${PGID}'
- TZ: !ENV '${TZ}'
- NODE_ENV: !ENV '${NODE_ENV}'
- ACTUAL_PORT: !ENV '${ACTUAL_PORT}'
- ACTUAL_HOSTNAME: !ENV '${ACTUAL_HOSTNAME}'
- ACTUAL_DATA_DIR: !ENV '${ACTUAL_DATA_DIR}'
volumes:
- actual_data: /data
volumes:
actual_data:
device: '/containers/actual/data'Makejail:
# Makejail
ARG tag=latest
OPTION container=boot
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/actual:${tag}
Save the files above, then run appjail-director up.
Warning
Bastille's OCI support is experimental. It requires buildah and shares the host network stack (inherit). Mount volumes with --volume HOST JAIL; without it, image-declared volumes are stored under ${bastille_volumesdir}/${jail}.
services:
actual:
name: actual
image: "ghcr.io/daemonless/actual:latest"
network:
- mode: host
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- NODE_ENV=production
- ACTUAL_PORT=5006
- ACTUAL_HOSTNAME=0.0.0.0
- ACTUAL_DATA_DIR=/data
volumes:
- "/containers/actual/data:/data"Save as bastille-compose.yml, then run bastille up.
| Variable | Default | Description |
|---|---|---|
PUID |
1000 |
User ID for the application process |
PGID |
1000 |
Group ID for the application process |
TZ |
Etc/UTC |
Timezone for the container |
NODE_ENV |
production |
Node runtime mode; leave as 'production' |
ACTUAL_PORT |
5006 |
Internal port the sync server listens on; leave as 5006 |
ACTUAL_HOSTNAME |
0.0.0.0 |
Bind address for the sync server; leave as 0.0.0.0 (the app's own default, '::', is IPv6-any and breaks single-stack containers) |
ACTUAL_DATA_DIR |
/data |
Directory where Actual stores its account database, budget files and server config (under the /data volume) |
| Path | Description |
|---|---|
/data |
Application data — account database, budget files and server config |
Architectures: amd64
User: bsd (UID/GID via PUID/PGID, defaults to 1000:1000)
Base: FreeBSD 15
Need help? Join our Discord community.