Skip to content

Self-Hosting

The DUUMBI registry ships as a single Docker image. It uses SQLite for metadata and the local filesystem for module archives — no external databases or services required.

  • Docker and Docker Compose v2+
  • (Optional) A reverse proxy for TLS — Caddy, nginx, or Traefik
Terminal window
# Generate a JWT secret (required)
export JWT_SECRET=$(openssl rand -hex 32)
# Start the registry
docker compose up -d

The registry is now available at http://localhost:8080. Verify:

Terminal window
curl http://localhost:8080/health
# ok

Users can register at http://localhost:8080/register and manage API tokens at /settings/tokens.

All configuration is via environment variables:

VariableDefaultDescription
DUUMBI_PORT8080Server listen port
DUUMBI_DBregistry.dbSQLite database path
DUUMBI_STORAGEstorageModule archive directory
RUST_LOGduumbi_registry=infoLog level (tracing)
AUTH_MODElocal_passwordAuth mode: local_password or github_oauth
JWT_SECRETRequired. Secret for signing session tokens
BASE_URLhttp://localhost:8080Public URL (used in OAuth callbacks)
GITHUB_CLIENT_IDGitHub OAuth app client ID
GITHUB_CLIENT_SECRETGitHub OAuth app client secret
services:
registry:
build: .
ports:
- "9090:8080"
volumes:
- ./my-data:/data
environment:
- JWT_SECRET=${JWT_SECRET}
- RUST_LOG=duumbi_registry=debug,tower_http=debug

In production, place a reverse proxy in front of the registry for TLS termination.

registry.mycompany.com {
reverse_proxy localhost:8080
}

Caddy automatically provisions Let’s Encrypt certificates.

server {
listen 443 ssl;
server_name registry.mycompany.com;
ssl_certificate /etc/letsencrypt/live/registry.mycompany.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/registry.mycompany.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

All data is stored under /data in the container:

  • /data/registry.db — SQLite database (modules, versions, users, tokens)
  • /data/modules/.tar.gz archives organized as @scope/name/version.tar.gz

The default docker-compose.yml uses a named volume (registry-data) that persists across container restarts and rebuilds.

Terminal window
# Backup
docker compose exec registry cp /data/registry.db /data/registry.db.bak
docker compose cp registry:/data ./backup
# Restore
docker compose down
docker compose cp ./backup/. registry:/data
docker compose up -d

To use GitHub OAuth instead of local passwords:

  1. Create a GitHub OAuth App at github.com/settings/developers
  2. Set the callback URL to https://your-registry.example.com/auth/github/callback
  3. Configure the environment variables:
Terminal window
AUTH_MODE=github_oauth
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
JWT_SECRET=$(openssl rand -hex 32)
BASE_URL=https://your-registry.example.com

With GitHub OAuth enabled, users sign in via “Sign in with GitHub” on the web UI. The CLI supports a device code flow for headless authentication — see Authentication.

On the DUUMBI client side, point your project at the private registry:

Terminal window
# Add registry
duumbi registry add company https://registry.mycompany.com
# Authenticate
duumbi registry login company --token duu_your_token_here
# Use modules from the private registry
duumbi deps add @company/my-module

Or edit .duumbi/config.toml directly:

[registries]
company = "https://registry.mycompany.com"
[dependencies]
"@company/my-module" = { version = "1.0.0", registry = "company" }