Deploy templates
Every file below is read from the repository when the site is built, so it is the current template on
main. How to use each one, and which master-key setup it implies, is in
Deploy with Docker. All values in the templates are fake examples.
The templates default to the image tag latest; set KUUKURA_VERSION (or the image tag) to a release to pin one in
production.
Docker Compose + Caddy (any VPS)
Section titled “Docker Compose + Caddy (any VPS)”# Plain Docker Compose with automatic HTTPS (Caddy). Uses the published image; nothing is built from source.## 1. Create the master key once, so that ./secrets/master_key exists (see docs/deploy.md):# docker run --rm -v "$PWD/secrets:/s" ghcr.io/victorlunam/kuukura key init --key-file /s/master_key# 2. printf '%s' 'your-google-oauth-client-secret' > secrets/oidc_client_secret# 3. cp .env.example .env and edit it# 4. docker compose up -d# 5. docker compose exec kuukura /ku-server user invite you@example.com --admin# 6. docker compose exec -it kuukura /ku-server key show (once: store the key in your password manager)services: kuukura: image: ghcr.io/victorlunam/kuukura:${KUUKURA_VERSION:-latest} restart: unless-stopped environment: VAULT_DOMAIN: ${VAULT_DOMAIN} VAULT_STORAGE: sqlite VAULT_SQLITE_PATH: /data/kuukura.db VAULT_KEY_PROVIDER: manual VAULT_MASTER_KEY_FILE: /run/secrets/master_key VAULT_OIDC_CLIENT_ID: ${VAULT_OIDC_CLIENT_ID} VAULT_OIDC_CLIENT_SECRET_FILE: /run/secrets/oidc_client_secret secrets: [master_key, oidc_client_secret] volumes: [kuukura-data:/data] expose: ["8080"]
# Terminates HTTPS (Let's Encrypt) and forwards X-Forwarded-Proto / X-Forwarded-For, which the server needs. caddy: image: caddy:2 restart: unless-stopped ports: ["80:80", "443:443"] command: caddy reverse-proxy --from ${VAULT_DOMAIN} --to kuukura:8080 volumes: [caddy-data:/data] depends_on: [kuukura]
secrets: master_key: { file: ./secrets/master_key } oidc_client_secret: { file: ./secrets/oidc_client_secret }
volumes: kuukura-data: caddy-data:View deploy/docker/docker-compose.yml on GitHub
VAULT_DOMAIN=vault.example.comVAULT_OIDC_CLIENT_ID=000000000000-example.apps.googleusercontent.com# latest follows the newest release; pin a release tag (YEAR.MONTH.N) for productionKUUKURA_VERSION=latestView deploy/docker/.env.example on GitHub
Dokploy (Compose)
Section titled “Dokploy (Compose)”# Dokploy (Compose application, or the template in this folder). Dokploy's Traefik terminates HTTPS, so there is no# proxy here.## In Dokploy: Create Service -> Compose -> paste this file -> Environment tab: set the variables below -># Domains tab: add your domain, service `kuukura`, port 8080, HTTPS on (Let's Encrypt).## Variables (Environment tab):# VAULT_DOMAIN vault.example.com (the same domain you add in the Domains tab)# VAULT_OIDC_CLIENT_ID 000000000000-example.apps.googleusercontent.com# VAULT_OIDC_CLIENT_SECRET the Google OAuth client secret# KUUKURA_VERSION a release tag (YEAR.MONTH.N, see the releases page); default `latest`, pin one in production## The master key is generated on the first start and kept in the `kuukura-keys` volume, apart from the database# in `kuukura-data`. After the first start, open a terminal in the container and run `ku-server key show` once:# store the key in your password manager (docs/deploy.md). The OAuth client secret is an environment variable:# anyone who can read this service's environment (Dokploy admins, `docker inspect`) can read it.services: kuukura: image: ghcr.io/victorlunam/kuukura:${KUUKURA_VERSION:-latest} restart: unless-stopped environment: VAULT_DOMAIN: ${VAULT_DOMAIN} VAULT_STORAGE: sqlite VAULT_SQLITE_PATH: /data/kuukura.db VAULT_KEY_PROVIDER: manual VAULT_MASTER_KEY_FILE: /keys/master_key VAULT_MASTER_KEY_GENERATE: "true" VAULT_OIDC_CLIENT_ID: ${VAULT_OIDC_CLIENT_ID} VAULT_OIDC_CLIENT_SECRET: ${VAULT_OIDC_CLIENT_SECRET} volumes: - kuukura-data:/data - kuukura-keys:/keys expose: - "8080" healthcheck: test: ["CMD", "/ku-server", "healthcheck"] interval: 30s timeout: 5s retries: 3 start_period: 10s
volumes: kuukura-data: kuukura-keys:View deploy/dokploy/docker-compose.yml on GitHub
Dokploy (template)
Section titled “Dokploy (template)”# Dokploy template (blueprint) for kuukura: copy docker-compose.yml, template.toml and kuukura.svg to# blueprints/kuukura/ in Dokploy/templates and add the entry from meta.json to its meta.json.## The master key is generated by the server on the first start (see docker-compose.yml), so nothing secret is# generated here. The OIDC values are inputs: create a Google OAuth client (type "Web application") with the# redirect URI https://<your domain>/auth/callback, then fill VAULT_OIDC_CLIENT_ID and VAULT_OIDC_CLIENT_SECRET in the# Environment tab before the first deploy, and set the domain you actually use in the Domains tab.
# Pinning: this repository keeps KUUKURA_VERSION = "latest" and meta.json "version": "latest". Dokploy/templates wants# a pinned image, so in the pull request set both to the current release tag (YEAR.MONTH.N) and the image in# docker-compose.yml follows through ${KUUKURA_VERSION}.## Dokploy creates the template's domain with certificateType "none" and a generated host: after deploying, open the# Domains tab, set your real domain, turn HTTPS on (Let's Encrypt), and set VAULT_DOMAIN to the same domain.
[variables]main_domain = "${domain}"
[config][[config.domains]]serviceName = "kuukura"port = 8080host = "${main_domain}"
[config.env]VAULT_DOMAIN = "${main_domain}"VAULT_OIDC_CLIENT_ID = ""VAULT_OIDC_CLIENT_SECRET = ""KUUKURA_VERSION = "latest"View deploy/dokploy/template.toml on GitHub
{ "id": "kuukura", "name": "kuukura", "version": "latest", "description": "A self-hosted secrets vault for small teams: envelope encryption, per-tag grants, an audit log, passkey or authenticator-app confirmations for reveals.", "logo": "kuukura.svg", "links": { "github": "https://github.com/victorlunam/kuukura", "website": "https://github.com/victorlunam/kuukura", "docs": "https://github.com/victorlunam/kuukura/blob/main/docs/deploy.md" }, "tags": ["security", "secrets", "self-hosted"]}View deploy/dokploy/meta.json on GitHub
Coolify
Section titled “Coolify”# documentation: https://github.com/victorlunam/kuukura/blob/main/docs/deploy.md# slogan: Self-hosted secrets vault for small teams, with an audit log and confirmations by passkey.# category: security# tags: secrets,vault,security,encryption,self-hosted# logo: svgs/kuukura.svg# port: 8080
# Coolify service template. The master key is generated on the first start and kept in the kuukura-keys volume,# apart from the database in kuukura-data. After the first deploy open the service's terminal and run# `/ku-server key show` once; store the key in your password manager.# Before deploying, create a Google OAuth client (type "Web application") with the redirect URI# https://<the domain Coolify assigns>/auth/callback and fill the two OIDC variables.services: kuukura: image: ghcr.io/victorlunam/kuukura:${KUUKURA_VERSION:-latest} environment: - SERVICE_FQDN_KUUKURA_8080 - VAULT_DOMAIN=${SERVICE_FQDN_KUUKURA} - VAULT_STORAGE=sqlite - VAULT_SQLITE_PATH=/data/kuukura.db - VAULT_KEY_PROVIDER=manual - VAULT_MASTER_KEY_FILE=/keys/master_key - VAULT_MASTER_KEY_GENERATE=true - VAULT_OIDC_CLIENT_ID=${VAULT_OIDC_CLIENT_ID:?} - VAULT_OIDC_CLIENT_SECRET=${VAULT_OIDC_CLIENT_SECRET:?} volumes: - kuukura-data:/data - kuukura-keys:/keys healthcheck: test: ["CMD", "/ku-server", "healthcheck"] interval: 30s timeout: 5s retries: 3 start_period: 10s
volumes: kuukura-data: kuukura-keys:View deploy/coolify/kuukura.yaml on GitHub
Render
Section titled “Render”# Render Blueprint: deploy kuukura from the published image with one persistent disk for the SQLite database.# Render terminates HTTPS. On Render the master key lives in Render's environment (VAULT_MASTER_KEY, generated once):# read it from the service's Environment tab and store a copy in your password manager (docs/deploy.md).# `image.url` follows `latest`; for production replace it with a release tag (see docs/deploy.md). Render asks for# the `sync: false` values (VAULT_DOMAIN and the two OIDC values) when the Blueprint is created. VAULT_DOMAIN is the# domain the service is served on (the onrender.com name, or your custom domain); change it in the Environment tab# if you add a custom domain. Render's default port is 10000, so VAULT_ADDR listens there.services: - type: web name: kuukura runtime: image image: url: ghcr.io/victorlunam/kuukura:latest plan: 0.5c-512mb healthCheckPath: /healthz disk: name: kuukura-data mountPath: /data sizeGB: 1 envVars: - key: VAULT_DOMAIN sync: false - key: VAULT_ADDR value: ":10000" - key: VAULT_STORAGE value: sqlite - key: VAULT_SQLITE_PATH value: /data/kuukura.db - key: VAULT_KEY_PROVIDER value: manual - key: VAULT_MASTER_KEY generateValue: true - key: VAULT_OIDC_CLIENT_ID sync: false - key: VAULT_OIDC_CLIENT_SECRET sync: falseDocker Swarm / Portainer stack
Section titled “Docker Swarm / Portainer stack”# Docker Swarm stack (Portainer "Stack" works too). Uses real Docker secrets. Run ONE replica: kuukura keeps# login state in memory and SQLite has a single writer.## docker network create --driver overlay --attachable proxy (once; skip if your proxy already made it)# printf '1:%s\n' "$(head -c 32 /dev/urandom | base64)" | docker secret create kuukura_master_key -# (then run `ku-server key show` once in a terminal in the container and store the key in your password# manager, see docs/deploy.md)# printf '%s' 'google-oauth-client-secret' | docker secret create kuukura_oidc_secret -# VAULT_DOMAIN=vault.example.com VAULT_OIDC_CLIENT_ID=... docker stack deploy -c stack.yml kuukura## Put your own HTTPS reverse proxy (Traefik/Caddy/nginx) on the `proxy` network and point it at kuukura:8080.services: kuukura: image: ghcr.io/victorlunam/kuukura:${KUUKURA_VERSION:-latest} environment: VAULT_DOMAIN: ${VAULT_DOMAIN} VAULT_STORAGE: sqlite VAULT_SQLITE_PATH: /data/kuukura.db VAULT_KEY_PROVIDER: manual VAULT_MASTER_KEY_FILE: /run/secrets/kuukura_master_key VAULT_OIDC_CLIENT_ID: ${VAULT_OIDC_CLIENT_ID} VAULT_OIDC_CLIENT_SECRET_FILE: /run/secrets/kuukura_oidc_secret secrets: [kuukura_master_key, kuukura_oidc_secret] volumes: [kuukura-data:/data] networks: [proxy] deploy: replicas: 1 update_config: { order: stop-first } restart_policy: { condition: on-failure }
secrets: kuukura_master_key: { external: true } kuukura_oidc_secret: { external: true }
volumes: kuukura-data:
networks: proxy: external: true