Installation
This guide covers a production deployment on a fresh Linux VM. The result is a working HTTPS installation with automatic TLS, persistent data, and an initial administrator account.
What You Will Need
- A Linux VM (AlmaLinux 9, Ubuntu 22.04+, or Debian 12+) with at least 2 vCPU and 4 GB RAM
- A domain name with an A record pointing to the VM's public IP
- Port 80 and 443 open in the firewall (for TLS certificate issuance)
- Root or sudo access
The stack can run with Docker Compose or Podman Compose. The examples use the docker compose command; on Podman-based hosts, use podman compose where needed.
Step 1 — Install the Container Runtime
Verify:
Step 2 — Install Caddy (Reverse Proxy)
Caddy handles HTTPS automatically — it obtains and renews a Let's Encrypt certificate for your domain with no extra configuration.
apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl gnupg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt-get update && apt-get install -y caddy
Step 3 — Clone the Repository
Step 4 — Configure the Stack
Open docker-compose.yml and fill in the deployment-specific values:
Update these fields:
| Field | What to use |
|---|---|
POSTGRES_PASSWORD in x-postgres-env |
Run: openssl rand -base64 32 |
APP_BASE_URL |
Your public HTTPS URL, for example https://karvio.example.org |
CHANGE_ME_STRONG_ADMIN_PASSWORD |
Choose a strong password for the initial admin user |
CHANGE_ME_LONG_RANDOM_SECRET |
Run: openssl rand -base64 64 |
Also set BOOTSTRAP_ENABLED: "true" — this creates the admin account on first start.
Warning
After the first successful login, set BOOTSTRAP_ENABLED back to "false" and restart the backend. Leaving it enabled means every restart re-runs the bootstrap logic.
Generate secrets in one go:
Do not commit secret values back to version control. Keep the production compose file on the server or manage the values through your deployment secret store.
Step 5 — Start the Stack
The first run builds images and may take a few minutes. On subsequent starts, images are cached.
Check that all containers are running and healthy:
Expected output: postgres and backend should be healthy; frontend and procrastinate-worker should be up.
NAME STATUS
karvio_postgres_1 Up (healthy)
karvio_backend_1 Up (healthy)
karvio_procrastinate-worker_1 Up
karvio_frontend_1 Up
If backend is stuck waiting, check logs:
The backend waits for PostgreSQL to accept connections before running migrations. This is normal on first start and takes a few seconds.
Step 6 — Configure Caddy
Create the Caddy config and replace karvio.example.com with your domain:
Start Caddy and enable it on boot:
Caddy immediately starts obtaining a TLS certificate. This usually takes 10-30 seconds and requires port 80 to be reachable from the internet.
Verify the certificate was issued:
Look for certificate obtained in the logs. Once done, your site is live at https://your-domain.
Step 7 — Log In and Disable Bootstrap
Open your Karvio URL in a browser. Log in with:
- Username:
admin - Password: the value you set for
ADMIN_PASSWORD
After logging in, disable bootstrap so it does not re-run on future restarts:
# Edit docker-compose.yml: set BOOTSTRAP_ENABLED: "false"
nano docker-compose.yml
# Recreate the backend container to pick up the change
docker compose -f docker-compose.yml up -d backend
Verifying the Installation
# Frontend and API through Caddy
curl -fsS https://your-domain/api/v1/version
# Backend status directly from the host
curl -fsS http://127.0.0.1:8080/status
# PostgreSQL
docker compose -f docker-compose.yml exec postgres \
sh -c 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
Backups
PostgreSQL
mkdir -p backups
docker compose -f docker-compose.yml exec -T postgres \
sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom' \
> backups/karvio-$(date +%Y%m%d-%H%M%S).dump
File Storage (Attachments and Artifacts)
docker compose -f docker-compose.yml cp backend:/app/data/attachments backups/attachments
docker compose -f docker-compose.yml cp backend:/app/data/performance_artifacts backups/performance_artifacts
Configuration
Automate database, file storage, and configuration backups with a daily job and store copies off-host.
Updating Karvio
Always take a backup before updating.
Database migrations run automatically on backend startup.
Rolling Back
If the update fails and no migrations changed data:
git checkout <previous-tag-or-commit>
docker compose -f docker-compose.yml build
docker compose -f docker-compose.yml up -d
If migrations already ran, restore the PostgreSQL backup taken before the update:
docker compose -f docker-compose.yml stop backend procrastinate-worker frontend
cat backups/karvio-YYYYMMDD-HHMMSS.dump | \
docker compose -f docker-compose.yml exec -T postgres \
sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists'
git checkout <previous-tag-or-commit>
docker compose -f docker-compose.yml build
docker compose -f docker-compose.yml up -d
Logs
# All services
docker compose -f docker-compose.yml logs -f
# One service
docker compose -f docker-compose.yml logs -f backend
Configuration Reference
All settings go in the environment section of docker-compose.yml. See config.example for the full list.
| Setting | Default | Description |
|---|---|---|
POSTGRES_USER |
karvio |
Database user |
POSTGRES_PASSWORD |
— | Database password — must be set |
POSTGRES_DB |
karvio |
Database name |
APP_BASE_URL |
— | Public HTTPS URL — must be set |
ADMIN_PASSWORD |
— | Initial admin password — must be set |
AUTH_SECRET |
— | Session signing key — must be set, 64+ random bytes |
BOOTSTRAP_ENABLED |
false |
Create admin account on startup. Enable for first run only. |
LOG_JSON |
true |
Structured JSON logs |
METRICS_ENABLED |
true |
Prometheus-compatible /metrics endpoint |
AUDIT_RETENTION_DAYS |
365 |
Days to keep audit log entries |
AI_TEST_CASE_ASSISTANT_ENABLED |
false |
Enable AI test case generation |
AI_PROVIDER |
— | openai or other supported provider |
AI_MODEL |
— | Model name, e.g. gpt-4o-mini |
AI_API_KEY |
— | Server-side API key — never expose in frontend |