Skip to main content

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:

docker-compose.yml
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:

The first build takes a few minutes (the backend image pulls in Chromium and LibreOffice). Subsequent docker compose up calls reuse the cached images.

No login screen

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:

.env
FRONTEND_PORT=8080
BACKEND_PORT=8081
MONGO_PORT=27017
MONGO_DATABASE=velkin
PDF_TIMEOUT=30s
VariableDefaultPurpose
FRONTEND_PORT8080Host port for the Studio (nginx)
BACKEND_PORT8081Host port for the API
MONGO_PORT27017Host port for MongoDB
MONGO_DATABASEvelkinDatabase name the backend uses
PDF_TIMEOUT30sMax 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

VariableDefaultPurpose
HTTP_ADDR:8080Address/port the API listens on.
MONGO_URImongodb://localhost:27017MongoDB connection string.
MONGO_DATABASEvelkinDatabase name.
CHROME_PATH(auto)Path to the Chromium binary.
SOFFICE_PATH(auto)Path to the LibreOffice soffice binary.
SHUTDOWN_TIMEOUT10sGrace period for in-flight requests on shutdown.
MAX_BINARY_UPLOAD_BYTES16777216 (16 MiB)Max size for a template upload / HTML content.
MAX_JSON_BODY_BYTES0 (no limit)Cap on JSON request bodies. 0 disables the cap. Set a value to bound render data payloads.

Rendering & timeouts

VariableDefaultPurpose
PDF_TIMEOUT30sMax time for a single Chromium PDF render.
SOFFICE_TIMEOUT30sMax 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.

VariableDefaultEffect when true
PDF_ALLOW_NO_SANDBOXfalsePass --no-sandbox to Chromium (for runtimes without namespaces).
PDF_DISABLE_NETWORK_FILTERfalseTurn off subresource interception entirely.
PDF_ALLOW_PRIVATE_NETWORKfalseAllow templates to fetch private/loopback/link-local addresses.
PDF_ALLOW_JAVASCRIPTfalseAllow 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.