Skip to content

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.

deploy/docker/docker-compose.yml
# 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

deploy/docker/.env.example
VAULT_DOMAIN=vault.example.com
VAULT_OIDC_CLIENT_ID=000000000000-example.apps.googleusercontent.com
# latest follows the newest release; pin a release tag (YEAR.MONTH.N) for production
KUUKURA_VERSION=latest

View deploy/docker/.env.example on GitHub

deploy/dokploy/docker-compose.yml
# 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

deploy/dokploy/template.toml
# 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 = 8080
host = "${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

deploy/dokploy/meta.json
{
"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

deploy/coolify/kuukura.yaml
# 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.yaml
# 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: false

View render.yaml on GitHub

deploy/swarm/stack.yml
# 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

View deploy/swarm/stack.yml on GitHub