Docker, Compose & one-click PaaS
Distroless/Debian images, Compose deployment, health checks, Podman, and the Coolify/Dokploy/EasyPanel templates.
Revka ships as a single self-contained binary, which makes it a natural fit for containers. This page covers running Revka in Docker — the two published images, a production docker-compose.yml, the container environment reference, the HEALTHCHECK probe, and Podman differences — and then the three one-click PaaS templates (Coolify, Dokploy, EasyPanel) plus the workflow that keeps them in sync with each release.
Use this page when you want Revka running as a long-lived gateway on a server or PaaS host. If you instead want Revka managed by your OS init system on the host directly, see Run as a background service. For how the in-container runtime.kind sandbox differs from the host runtime, see Runtime modes, adapters & resource limits.
Container images
Section titled “Container images”Two multi-arch images are published to GitHub Container Registry. They share the same build but differ in their runtime base.
| Image | Base | Shell | Use it when |
|---|---|---|---|
ghcr.io/kumihoio/revka:latest | gcr.io/distroless/cc-debian13:nonroot | None | You want the smallest attack surface and the agent does not need shell tools. |
ghcr.io/kumihoio/revka:debian | debian:bookworm-slim (ships bash, git, curl, ca-certificates) | Yes | The agent needs shell-based tools (ls, git, curl, etc.). |
Both images use the same entrypoint — they run revka gateway, the gateway server only (webhooks and WebSockets). The gateway does not start channels, the heartbeat, or the scheduler; to get the full autonomous runtime, override the command to daemon (e.g. docker run ... ghcr.io/kumihoio/revka:latest daemon). Both run as the unprivileged nobody user (UID 65534), expose port 42617, and set HOME=/revka-data with the workspace at /revka-data/workspace.
Quick start
Section titled “Quick start”Provide an API key at runtime, persist /revka-data in a named volume, and publish the gateway port:
docker run -d \ --name revka \ -e API_KEY=sk-or-... \ -e PROVIDER=openrouter \ -v revka-data:/revka-data \ -p 42617:42617 \ ghcr.io/kumihoio/revka:latestThen open the dashboard at http://localhost:42617/. The image bakes a default config with allow_public_bind = true, so the gateway is reachable from outside the container. Pairing is enabled by default (require_pairing = true), so first access requires completing the pairing flow.
Image build arguments
Section titled “Image build arguments”Both Dockerfiles accept a single build argument controlling which Cargo feature flags are compiled in:
| Build ARG | Default | Meaning |
|---|---|---|
REVKA_CARGO_FEATURES | channel-lark,whatsapp-web | Comma-separated Cargo feature list passed to cargo build --release --locked --features. Set to empty to build the default feature set only. |
# Build the distroless image with a custom feature setdocker build --build-arg REVKA_CARGO_FEATURES="channel-lark" -t revka:custom .
# Build the Debian (shell-equipped) variantdocker build -f Dockerfile.debian -t revka:debian .The build is a multi-stage pipeline: a web-builder stage (Node 22 Alpine) compiles the dashboard, a builder stage (Rust 1.95) compiles the binary with registry/target build caches, and the final stage copies the stripped binary into the distroless or Debian runtime. The build fails fast if the resulting binary is under 1 MB, which catches dummy/cache-only build artifacts. See Cargo feature flags & ADRs for the full feature list.
Container environment reference
Section titled “Container environment reference”The image bakes a working config.toml into /revka-data/.revka/config.toml. You normally only need to supply an API key; everything else has a container-appropriate default. Environment variables override the baked config at runtime.
| Variable | Default | Meaning |
|---|---|---|
API_KEY / REVKA_API_KEY | empty | LLM provider API key. Required — must be supplied at runtime. |
PROVIDER | openrouter | LLM provider id. Both published images leave this unset; the baked config value (openrouter) wins. Only the local-dev dev build target sets PROVIDER=ollama via ENV. |
REVKA_MODEL | provider default | Model override (e.g. anthropic/claude-sonnet-4-20250514). |
REVKA_ALLOW_PUBLIC_BIND | true (set in Compose/templates) | Required for container networking — the gateway refuses non-loopback binds otherwise. |
REVKA_GATEWAY_PORT | 42617 | Gateway listen port inside the container. |
LANG | C.UTF-8 | UTF-8 locale so CJK / multibyte input is handled correctly. |
HOME | /revka-data | Home directory; config lives at /revka-data/.revka/. |
REVKA_WORKSPACE | /revka-data/workspace | Agent workspace path. |
Provider-specific key variables (OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, and others) also work and take precedence over the generic API_KEY. For the full environment-override list, see Configuration overview.
Docker Compose deployment
Section titled “Docker Compose deployment”The repository ships a reference docker-compose.yml tuned for production: restart: unless-stopped, resource limits, a named volume, and the health check wired in. Supply the API key on the command line or via a .env file.
services: revka: image: ghcr.io/kumihoio/revka:latest # For ARM64 where distroless exits immediately, switch to: # image: ghcr.io/kumihoio/revka:debian container_name: revka restart: unless-stopped environment: - API_KEY=${API_KEY:-} - PROVIDER=${PROVIDER:-openrouter} - REVKA_ALLOW_PUBLIC_BIND=true - REVKA_GATEWAY_PORT=${REVKA_GATEWAY_PORT:-42617} volumes: - revka-data:/revka-data ports: - "${HOST_PORT:-42617}:${REVKA_GATEWAY_PORT:-42617}" deploy: resources: limits: cpus: "2" memory: 512M reservations: cpus: "0.5" memory: 32M healthcheck: test: ["CMD", "revka", "status", "--format=exit-code"] interval: 60s timeout: 10s retries: 3 start_period: 10s
volumes: revka-data:Lifecycle commands:
# Start in the background (inline key)API_KEY=sk-or-... docker compose up -d
# Stop and remove the container (the named volume is kept)docker compose down
# Follow logsdocker compose logs -f revka
# Publish on a different host port (e.g. 42617 is already taken)HOST_PORT=8080 docker compose up -dOr keep settings in a .env file next to the Compose file:
API_KEY=sk-or-...PROVIDER=openrouterHOST_PORT=42617REVKA_GATEWAY_PORT=42617| Variable | Default | Meaning |
|---|---|---|
API_KEY | empty | Provider API key (required). |
PROVIDER | openrouter | Provider id. |
HOST_PORT | 42617 | Host-side port in the ports mapping. |
REVKA_GATEWAY_PORT | 42617 | Container-side gateway port. |
The committed resource limits cap the container at 2 CPUs / 512 MB and reserve 0.5 CPU / 32 MB. Revka’s footprint is small, so these are comfortable defaults for a single instance; raise the limits if you run many channels or heavy MCP sidecars.
Health checks
Section titled “Health checks”The local-build Dockerfiles (Dockerfile and Dockerfile.debian) and the Compose file define this liveness probe. The published GHCR images (:latest and :debian) have no built-in HEALTHCHECK — the Compose-level healthcheck block (or docker run --health-cmd) is what provides liveness when using pulled images:
healthcheck: test: ["CMD", "revka", "status", "--format=exit-code"] interval: 60s timeout: 10s retries: 3 start_period: 10srevka status --format=exit-code is the canonical container liveness check. Instead of printing the human-readable status report, it performs a lightweight GET /health against the configured gateway and exits 0 when the gateway is healthy and 1 otherwise. The probe normalizes a bind host of [::] or 0.0.0.0 to 127.0.0.1, uses the configured gateway.port, and applies a 5-second timeout. The start_period gives the daemon 10 seconds to bind before failures count against the retry budget.
Run it manually against a running container:
docker exec revka revka status --format=exit-code; echo "exit=$?"Podman
Section titled “Podman”The images and Compose file run unchanged under Podman, with two adjustments for rootless Podman’s user-namespace and SELinux handling:
- Add
--userns keep-idso the container’s UID65534maps cleanly to your host user and the volume stays writable. - Append the
:Zsuffix to the volume mount so SELinux relabels it for the container.
podman run -d \ --name revka \ --userns keep-id \ -e API_KEY=sk-or-... \ -e PROVIDER=openrouter \ -v revka-data:/revka-data:Z \ -p 42617:42617 \ ghcr.io/kumihoio/revka:latestpodman compose reads the same docker-compose.yml. Everything else — environment variables, the health check, the published port — is identical to Docker.
One-click PaaS templates
Section titled “One-click PaaS templates”Revka has draft templates for three self-hosted PaaS platforms. Each provisions the same container, a revka-data volume, port 42617, and the standard health check; they differ only in how the platform expects images to be pinned and how it generates the API-key secret.
Coolify
Section titled “Coolify”The Coolify template (marketplace/coolify/revka.yaml) is a Compose service that targets the ghcr.io/kumihoio/revka:latest tag, so Coolify users pick up new versions on redeploy. Coolify generates a random API key for you via its ${SERVICE_PASSWORD_APIKEY} helper.
- In Coolify, create a New Service → Compose (or use the community template once merged).
- Set the
PROVIDERenvironment variable (defaultopenrouter) and supply or accept the auto-generatedAPI_KEY. - Deploy. Port
42617is exposed; resources default to 0.5–2 CPU and 32M–512M memory.
Dokploy
Section titled “Dokploy”The Dokploy blueprint (marketplace/dokploy/blueprints/revka/) requires a pinned image — Dokploy does not allow the latest tag — so it references a CalVer tag such as ghcr.io/kumihoio/revka:2026.4.21. The blueprint ships a docker-compose.yml and a template.toml that generates a domain and a 64-character API key.
[variables]main_domain = "${domain}"api_key = "${password:64}"
[config]env = [ "API_KEY=${api_key}", "PROVIDER=openrouter", "REVKA_ALLOW_PUBLIC_BIND=true", "REVKA_GATEWAY_PORT=42617"]
[[config.domains]]serviceName = "revka"port = 42617host = "${main_domain}"- In Dokploy, open Templates and search for “Revka” (once the listing is merged).
- Set
API_KEY(or accept the generated one) andPROVIDER. - Deploy. Dokploy assigns the generated domain to the service on port
42617.
EasyPanel
Section titled “EasyPanel”The EasyPanel template (marketplace/easypanel/) also requires a pinned image and exposes a schema-driven form. Its meta.yaml defines four fields:
| Field | Default | Meaning |
|---|---|---|
appServiceName | revka | Service name within EasyPanel. |
appServiceImage | ghcr.io/kumihoio/revka:2026.4.21 | Pinned image tag. |
apiKey | empty | LLM provider API key. |
provider | openrouter | One of openrouter, openai, anthropic, ollama. |
- In EasyPanel, go to Apps → Browse Templates → “Revka”.
- Fill in the API key and select a provider.
- Deploy. After deployment, the gateway is reachable on port
42617.
Marketplace auto-sync workflow
Section titled “Marketplace auto-sync workflow”A GitHub Actions workflow (.github/workflows/sync-marketplace-templates.yml) keeps the listings current. After each stable release — it runs once the Docker images are built and pushed to GHCR — it opens pull requests to the Coolify, Dokploy, and EasyPanel upstream repositories with the new image tag.
- Input:
release_tag— the CalVer tag (e.g.2026.4.21). - Secret:
MARKETPLACE_PAT— a GitHub token withrepo+workflowscopes that can push to theKumihoIOforks of the three upstream repos and open PRs. - Trigger: automatic from the stable-release pipeline after the Docker job; manual dispatch is also available.
Currently only the Dokploy sync job is active. The Coolify job is disabled (if: false) because its upstream blocked the bot account (disabled 2026-05-08), and the EasyPanel job is also disabled (if: false) at the maintainer’s request (disabled 2026-05-01). Coolify is not being auto-PR’d. The Dokploy job rewrites the pinned image tag each release. The very first listing for each active platform must be submitted as a manual PR before auto-sync takes over.