Self-hosting & deployment
Calco ships in two forms. This guide covers the enterprise, self-hosted distribution — a single binary (or Docker image) you run on your own infrastructure. If you are using the Cloud subscription on the Unikia platform, there is nothing to install; sign in and start rendering.
The Calco binary and Docker image are sold exclusively and delivered privately to enterprise customers under contract. This guide is public so you can evaluate the operational model, but the artifacts themselves are provided to you directly — they are not downloadable from a public registry.
What you run
Everything is one statically linked Go binary, calco, with subcommands. For a
server deployment you run:
calco serve # HTTP API + web UI on :8080The binary serves the API, the web dashboard, and the MCP endpoint from a single
process. The rest of this page assumes calco serve; for the one-shot CLI
commands (convert, sign, verify, storage migrate) see the
CLI reference.
Runtime prerequisites
Rendering shells out to two system tools. They are already bundled in the Docker image — you only need to install them yourself when running the bare binary:
| Tool | Used for |
|---|---|
LibreOffice (libreoffice-calc, libreoffice-writer) | XLSX/DOCX → PDF conversion |
poppler-utils (pdftocairo, pdftoppm, pdfinfo) | PDF → HTML/image rasterization |
On Debian/Ubuntu:
apt-get install -y libreoffice-calc libreoffice-writer poppler-utils fonts-dejavu-core ca-certificatesOption A — run the binary
Place the binary on the host, point it at a writable data directory, and start the server:
# Minimal: SQLite + local storage, zero config
export CALCO_DB_PATH=/var/lib/calco/app.db
export STORAGE_DRIVER=local
export STORAGE_LOCAL_ROOT=/var/lib/calco/templates
export WEB_DIR=/opt/calco/web # the bundled Next.js export
calco serve -addr :8080-addr defaults to :8080. Put the process behind your own TLS-terminating
reverse proxy (nginx, Traefik, a load balancer) and set PUBLIC_BASE_URL to the
externally reachable URL so generated links and the MCP resource URL are correct.
WEB_DIR must point at the static web export shipped alongside the binary. If it
is unset or wrong, the API still works but the dashboard route serves a
placeholder page.
Option B — Docker image
The image bundles the binary, the web export, and the LibreOffice/poppler runtime, so a deployment needs only the image plus configuration. Run it directly:
docker run -d --name calco \
-p 8080:8080 \
-v calco-data:/app/data \
--env-file calco.env \
<your-private-registry>/calco:1.0.0The container's default command is calco serve. It listens on 8080, stores
SQLite and local blobs under /app/data (mount a volume to persist them), and
reads configuration from the environment.
Docker Compose
services:
calco:
image: <your-private-registry>/calco:1.0.0
restart: unless-stopped
ports:
- "8080:8080"
env_file:
- calco.env
volumes:
- ./data:/app/dataIf you were given source rather than a prebuilt image, build it with the
included multi-stage Dockerfile (docker build -t calco:1.0.0 .) — it compiles
the web UI and the Go binary and assembles the runtime image in one step.
Configuration
All configuration is via environment variables. The only ones most deployments need are a database, a storage driver, and the session/auth settings; everything else has a sensible default.
Core / server
| Variable | Default | Purpose |
|---|---|---|
PUBLIC_BASE_URL | — | Externally reachable base URL; used for generated links and the MCP resource id. Set this in production. |
WEB_DIR | — | Path to the bundled Next.js static export (preset in the Docker image). |
SESSION_SECRET | — | Secret used to sign session cookies. Set a strong value in production. |
SESSION_TTL_HOURS | 24 | Session lifetime. |
TEMPLATE_LOCALE | — | Default locale for currency/date formatters. |
The listen address is the serve -addr flag (default :8080), not an env var.
Database
SQLite is the zero-config default and is fine for single-node deployments. Choose PostgreSQL (or AWS Aurora) for production HA.
| Variable | Default | Purpose |
|---|---|---|
CALCO_DB_DRIVER | sqlite | sqlite or postgres. |
CALCO_DB_PATH | /app/data/app.db | SQLite file path (sqlite driver). |
DATABASE_URL | — | Postgres DSN (required when driver is postgres), e.g. postgres://user:pass@host:5432/calco?sslmode=require. |
Schema migrations run automatically on boot. For Aurora, point DATABASE_URL at
the cluster writer endpoint — migrations and the first-admin bootstrap must
run against a writable endpoint.
Storage & cache
Template blobs and cached renders can live on the local filesystem or in S3 / MinIO. Local is the default.
| Variable | Default | Purpose |
|---|---|---|
STORAGE_DRIVER | local | local or s3. |
STORAGE_LOCAL_ROOT | — | Root directory for the local driver. |
STORAGE_LOCAL_PRESIGN_BASE_URL | — | Enables HMAC-signed /_presigned/* download URLs for local storage. |
STORAGE_S3_ENDPOINT / _REGION / _BUCKET / _KEY_PREFIX | — | S3 / MinIO target. |
STORAGE_S3_ACCESS_KEY / _SECRET_KEY | — | S3 credentials. |
STORAGE_S3_USE_PATH_STYLE | false | Set true for MinIO and other path-style endpoints. |
STORAGE_S3_PRESIGN_TTL | 5m | Lifetime of SigV4 presigned download URLs. |
CACHE_DRIVER | none | none, memory, local, or s3. |
CACHE_TTL / CACHE_MEMORY_MAX_MB | — | Cache lifetime and in-memory size cap. |
A presign-capable storage driver (S3, or local with
STORAGE_LOCAL_PRESIGN_BASE_URL) is required for MCP render_document, which
returns download links rather than inline bytes.
Authentication & OIDC
For a standalone deployment, bootstrap a local admin and you are done. To use your own identity provider, configure OIDC.
| Variable | Default | Purpose |
|---|---|---|
ADMIN_EMAIL / ADMIN_PASSWORD | — | Bootstrap the first admin on an empty database. |
LOCAL_AUTH_DISABLED | false | Disable local email/password login (OIDC-only). |
OIDC_ISSUER_URL | — | OIDC issuer; enables SSO and the MCP OAuth resource server. |
OIDC_CLIENT_ID / OIDC_CLIENT_SECRET | — | OIDC client credentials. |
OIDC_REDIRECT_URL | — | Callback URL (<PUBLIC_BASE_URL>/auth/oidc/callback). |
OIDC_ADMIN_ROLE | admin | Role/claim value that grants admin. |
OIDC_INTROSPECTION_URL | — | RFC 7662 endpoint; enables opaque-token validation for MCP OAuth. |
OIDC_REQUIRED_ENTITLEMENT | — | Restrict access to tokens carrying this entitlement. |
When OIDC is fully configured, the "create first admin" setup screen is suppressed and admins arrive via SSO.
MCP & batch limits
| Variable | Default | Purpose |
|---|---|---|
MCP_RESOURCE_URL | PUBLIC_BASE_URL + /mcp | Canonical MCP resource id / required token audience. |
MCP_RENDER_URL_TTL | 15m | Lifetime of a rendered-document download link. |
MCP_RENDER_RETENTION | 24h | How long stored render outputs are kept before sweeping. |
BATCH_MAX_ITEMS | 500 | Max records per batch request. |
BATCH_MAX_ARCHIVE_MB | 512 | Abort a batch once the archive exceeds this size. |
BATCH_RENDER_TIMEOUT | 10m | Per-request batch wall-clock timeout. |
First boot & health
On first boot against an empty database, Calco runs migrations and — if
ADMIN_EMAIL / ADMIN_PASSWORD are set — creates the initial admin and a default
organization. Otherwise, the web UI presents a one-time "create first admin"
screen.
Verify the server is healthy:
curl http://localhost:8080/health
# {"status":"ok","storage":"local","cache":"none"}The response echoes the active storage and cache drivers, which is a quick way to confirm your configuration was picked up.
Upgrading
Replace the binary or pull the new image tag and restart. Migrations for the new version run automatically on boot; on Postgres/Aurora they run against the writer endpoint. Always snapshot your database and storage before a major upgrade.