Installation
The recommended — and only supported — way to run Velkin is Docker Compose, for both development and small production setups. The stack builds the API and Studio from source and brings up MongoDB plus the rendering dependencies (Chromium + LibreOffice, bundled in the backend image) in one command, so there's nothing else to install.
Prerequisites
- Docker 24+ and Docker Compose v2
- Git to clone the repositories
- ~2 GB of free disk for the built images, plus storage for MongoDB
Project layout
Velkin's two services are built from source: the Go API lives in velkin-be
and the Angular Studio in velkin-fe. Clone both into the same working
directory and add a docker-compose.yml next to them:
my-velkin/
├── docker-compose.yml # the Compose file below
├── velkin-be/ # Go API → built as the `backend` service
└── velkin-fe/ # Angular Studio → built as the `frontend` service
Quick install
# 1. Create a working directory and clone the two services side by side
mkdir my-velkin && cd my-velkin
git clone https://github.com/B4ck01/velkin-be.git
git clone https://github.com/B4ck01/velkin-fe.git
Save this as docker-compose.yml in my-velkin/. It defines three services —
mongo, backend, and frontend:
name: velkin
services:
mongo:
image: mongo:7
restart: unless-stopped
volumes:
- mongo-data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 10s
timeout: 5s
retries: 6
start_period: 10s
ports:
- "${MONGO_PORT:-27017}:27017"
backend:
build:
context: ./velkin-be
restart: unless-stopped
depends_on:
mongo:
condition: service_healthy
security_opt:
- seccomp:unconfined # see "Chromium sandbox" below
environment:
HTTP_ADDR: ":8080"
MONGO_URI: "mongodb://mongo:27017"
MONGO_DATABASE: "${MONGO_DATABASE:-velkin}"
CHROME_PATH: "/usr/bin/chromium"
PDF_TIMEOUT: "${PDF_TIMEOUT:-30s}"
ports:
- "${BACKEND_PORT:-8081}:8080"
frontend:
build:
context: ./velkin-fe
restart: unless-stopped
depends_on:
- backend
ports:
- "${FRONTEND_PORT:-8080}:80"
volumes:
mongo-data:
Then build and boot the stack from the directory holding docker-compose.yml:
docker compose up --build # build + start
docker compose logs -f backend # tail one service
docker compose down # stop (keeps the mongo volume)
docker compose down -v # stop + wipe mongo data
The backend container listens on 8080 internally but is published on host
port 8081; the frontend (nginx) is published on host port 8080 and
proxies /api/* to the backend. So when the containers are healthy:
- Studio: http://localhost:8080
- API: http://localhost:8081 (also proxied at http://localhost:8080/api)
The first build takes a few minutes (the backend image pulls in Chromium and
LibreOffice). Subsequent docker compose up calls reuse the cached images.
Velkin has no built-in authentication — opening the Studio drops you straight into your projects. This is by design for trusted internal use; keep the service behind a LAN or VPN. See the Security model.
Chromium sandbox
The backend runs security_opt: seccomp:unconfined. Chromium re-enables its
sandbox by default, which creates user/PID/network namespaces via unshare();
Docker's default seccomp profile denies that without CAP_SYS_ADMIN. Loosening
seccomp is the upstream-recommended way to run Chromium in a container — the
sandbox itself still provides syscall filtering for the renderer processes.
If your platform can't relax seccomp, set PDF_ALLOW_NO_SANDBOX=true to pass
--no-sandbox instead. That weakens isolation, so prefer the seccomp approach.
Compose .env
To change the host ports or a couple of backend knobs, drop a .env file next
to docker-compose.yml — Compose loads it automatically. It only sets host
ports and a few backend knobs; everything else is passed inline in the compose
environment: block:
FRONTEND_PORT=8080
BACKEND_PORT=8081
MONGO_PORT=27017
MONGO_DATABASE=velkin
PDF_TIMEOUT=30s
| Variable | Default | Purpose |
|---|---|---|
FRONTEND_PORT | 8080 | Host port for the Studio (nginx) |
BACKEND_PORT | 8081 | Host port for the API |
MONGO_PORT | 27017 | Host port for MongoDB |
MONGO_DATABASE | velkin | Database name the backend uses |
PDF_TIMEOUT | 30s | Max time for a single Chromium PDF render |
Backend environment reference
The Go backend reads these variables directly. To override one beyond what the
compose file already sets, add it to the backend service's environment:
block.
Core
| Variable | Default | Purpose |
|---|---|---|
HTTP_ADDR | :8080 | Address/port the API listens on. |
MONGO_URI | mongodb://localhost:27017 | MongoDB connection string. |
MONGO_DATABASE | velkin | Database name. |
CHROME_PATH | (auto) | Path to the Chromium binary. |
SOFFICE_PATH | (auto) | Path to the LibreOffice soffice binary. |
SHUTDOWN_TIMEOUT | 10s | Grace period for in-flight requests on shutdown. |
MAX_BINARY_UPLOAD_BYTES | 16777216 (16 MiB) | Max size for a template upload / HTML content. |
MAX_JSON_BODY_BYTES | 0 (no limit) | Cap on JSON request bodies. 0 disables the cap. Set a value to bound render data payloads. |
Rendering & timeouts
| Variable | Default | Purpose |
|---|---|---|
PDF_TIMEOUT | 30s | Max time for a single Chromium PDF render. |
SOFFICE_TIMEOUT | 30s | Max time for a single LibreOffice conversion. |
Security escape hatches
These default to the safe setting. Only enable them when your environment genuinely requires it — each one widens the attack surface. See the Security model for the reasoning.
| Variable | Default | Effect when true |
|---|---|---|
PDF_ALLOW_NO_SANDBOX | false | Pass --no-sandbox to Chromium (for runtimes without namespaces). |
PDF_DISABLE_NETWORK_FILTER | false | Turn off subresource interception entirely. |
PDF_ALLOW_PRIVATE_NETWORK | false | Allow templates to fetch private/loopback/link-local addresses. |
PDF_ALLOW_JAVASCRIPT | false | Allow templates to execute JavaScript during PDF generation. |
Data & backups
MongoDB persists to the named mongo-data volume. To back it up:
# Dump to a local archive
docker compose exec mongo mongodump --archive --db velkin \
| gzip > velkin-$(date +%F).archive.gz
# Restore
gunzip -c velkin-2026-01-01.archive.gz \
| docker compose exec -T mongo mongorestore --archive
docker compose down keeps the volume; docker compose down -v deletes it.
Reverse proxy & TLS
Velkin has no built-in auth or TLS. In production, put it behind a reverse
proxy (nginx, Caddy, Traefik) on a private network or VPN, terminate TLS there,
and only expose the frontend service (which already proxies /api). See the
Security model.
Next step
Render your first PDF in a few minutes.