Deployment
Docker / Podman (recommended)
Pre-built images are available on GitHub Container Registry for amd64 and arm64:
docker run -d --name calrs \
-p 3000:3000 \
-v calrs-data:/var/lib/calrs \
-e CALRS_BASE_URL=https://cal.example.com \
ghcr.io/olivierlambert/calrs:latest
Podman works as a drop-in replacement — just use
podmaninstead ofdockerin all commands. The Containerfile (Dockerfile) is compatible with both runtimes.
To pin to a specific version: ghcr.io/olivierlambert/calrs:0.14.0
The image uses a multi-stage build:
- Builder:
rust:slim-trixie— compiles the release binary - Runtime:
debian:trixie-slim— minimal image with onlyca-certificates - Runs as unprivileged
calrsuser - Data stored in
/var/lib/calrs - Templates bundled at
/opt/calrs/templates/
To build from source instead: docker build -t calrs .
Docker Compose / Podman Compose
services:
calrs:
image: ghcr.io/olivierlambert/calrs:latest
ports:
- "3000:3000"
volumes:
- calrs-data:/var/lib/calrs
environment:
- CALRS_BASE_URL=https://cal.example.com
restart: unless-stopped
volumes:
calrs-data:
Works with both docker compose and podman-compose.
Binary + systemd
# Build from source
cargo build --release
# Install binary and templates
sudo cp target/release/calrs /usr/local/bin/
sudo cp -r templates /var/lib/calrs/templates
# Create a system user
sudo useradd -r -s /bin/false -m -d /var/lib/calrs calrs
# Install the service
sudo cp calrs.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now calrs
Edit /etc/systemd/system/calrs.service to set CALRS_BASE_URL.
systemd service
The included calrs.service has security hardening:
NoNewPrivileges=trueProtectSystem=strictProtectHome=trueReadWritePaths=/var/lib/calrsPrivateTmp=trueProtectKernelTunables=trueProtectControlGroups=trueRestart=on-failurewith 5-second delay
From source (development)
cargo build --release
calrs serve --port 3000
Then register at http://localhost:3000 — the first user becomes admin.
Reverse proxy
calrs listens on port 3000 by default. Put nginx or caddy in front for TLS.
nginx example
server {
listen 443 ssl http2;
server_name cal.example.com;
ssl_certificate /etc/letsencrypt/live/cal.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cal.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Caddy example
cal.example.com {
reverse_proxy localhost:3000
}
Environment variables
| Variable | Description | Default |
|---|---|---|
CALRS_DATA_DIR | SQLite database directory | /var/lib/calrs (Docker/systemd) or XDG (dev) |
CALRS_BASE_URL | Public URL (required for OIDC callbacks and email action links) | http://localhost:3000 |
RUST_LOG | Log level filter | calrs=info,tower_http=info |
Observability
calrs uses structured logging via the tracing crate. All log output goes to stderr, captured by systemd journal or Docker logs.
Log levels
# Default (recommended)
RUST_LOG=calrs=info,tower_http=info
# Verbose (includes per-request details)
RUST_LOG=calrs=debug,tower_http=debug
# Errors only
RUST_LOG=calrs=error
What’s logged
| Category | Level | Events |
|---|---|---|
| Auth | info/warn | Login success/failure, registration, logout, OIDC login |
| Bookings | info | Created, cancelled, approved, declined, reminder sent |
| CalDAV | info/error | Sync completed, write-back/delete failures, source added/removed |
| Admin | info/warn | Role changes, user toggle, config updates, impersonation |
| debug/error | Delivery success/failure | |
| HTTP | info | Every request (method, path, status, latency) |
| Database | info | Migrations applied on startup |
Viewing logs
# systemd
journalctl -u calrs -f
# Docker
docker logs -f calrs
Backup
The entire state is in a single SQLite file (calrs.db). To back up:
sqlite3 /var/lib/calrs/calrs.db ".backup /path/to/backup.db"
Or simply copy the file when the server is stopped.