Install
Acta is one process. It serves the API, the MCP endpoint and the web app from a single port, and keeps everything in SQLite plus a directory of attachments. There is no separate database server, queue or cache to run.
With Docker
The quickest route. Create a docker-compose.yml:
services:
acta:
image: ghcr.io/nubisco/acta:latest
pull_policy: always
container_name: acta
restart: unless-stopped
ports:
- '4460:4460'
environment:
TZ: Europe/Lisbon
ACTA_WORKSPACE: Acme
# The first administrator. Required when signing in with email codes,
# because somebody has to be able to invite everyone else.
ACTA_ADMIN_EMAIL: you@example.com
ACTA_ADMIN_HANDLE: you
ACTA_ADMIN_NAME: Your Name
# Only with an identity provider that pushes changes. Register
# https://<your-acta-host>/api/v1/platform/webhook with it, and put the
# secret it generates here. Unset, the endpoint answers 404.
# PLATFORM_WEBHOOK_SECRET: ''
volumes:
- acta-data:/data
healthcheck:
test:
- CMD
- bun
- -e
- "fetch('http://localhost:4460/healthz').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"
interval: 30s
timeout: 5s
volumes:
acta-data:Then:
docker compose up -dActa is on http://localhost:4460. Sign in with the admin email you set; by default the one-time code is printed to the container log:
docker compose logs -f actaThe default code sender is for getting started
[acta] OTP for you@example.com: 123456 in the log means anyone who can read your logs can sign in as anyone. Before you let other people in, either configure an email sender or put an identity provider in front.
Data
Everything lives in the /data volume: acta.sqlite and an attachments/ directory. Back up the volume and you have backed up Acta. Stop the container first, or copy the SQLite file with sqlite3 .backup rather than cp, so you do not capture a half-written page.
Behind a reverse proxy
Acta sets Secure on its session cookie, so it must be reached over HTTPS from anywhere but localhost. A minimal Caddy config:
acta.example.com {
reverse_proxy localhost:4460
}Set ACTA_BASE_URL=https://acta.example.com so links in outbound notifications point somewhere that works.
On Cloudflare Workers
Acta also runs on Workers with D1 for the database and R2 for attachments. This is how the hosted instance runs. server/wrangler.toml in the repository is a working example; the parts that matter are:
name = "acta"
main = "src/worker.ts"
compatibility_date = "2026-08-01"
[assets]
directory = "../web/dist"
binding = "ASSETS"
run_worker_first = true
html_handling = "none"
not_found_handling = "none"
[[d1_databases]]
binding = "DB"
database_name = "acta"
database_id = "..."
[[r2_buckets]]
binding = "ATTACHMENTS"
bucket_name = "acta-attachments"run_worker_first and html_handling = "none" are both load-bearing: the worker has to see every request in order to hand a signed-out visitor to your identity provider before any HTML is served.
Build the SPA and deploy:
pnpm -C web build
pnpm -C server exec wrangler deployThe schema migrates itself on first request, so there is no migration step to run. See Configuration for the variables.
From source
Requires Bun and pnpm.
git clone https://github.com/nubisco/acta
cd acta
pnpm install
pnpm dev # server on :4460, web dev server on :5173For a production build outside Docker:
pnpm build
ACTA_DATA_DIR=./data ACTA_WEB_DIST=./web/dist bun server/dist/index.jsUpgrading
Pull the new image and restart. Acta migrates its own schema on boot: new tables are created, new columns added, and the few changes SQLite cannot make in place are applied behind a guard so they run exactly once.
docker compose pull && docker compose up -dTake a backup first. Migrations are tested against databases in the old shape, but a backup is cheaper than trust.