Getting Started
Installation

Installation

One-Line Installer (Recommended)

The interactive installer works on Linux and macOS. It installs Docker if missing, starts the daemon, generates every secret, writes .env, applies database migrations, and brings the stack up.

curl -fsSL https://vaultctl.vinelabs.de/install.sh | bash

What it does:

  1. Checks for Docker and the Compose plugin (installs Docker if missing, starts the daemon)
  2. Downloads the vaultctl source into your chosen install directory
  3. Asks how to deploy:
    • Full stack — Caddy auto-TLS + vaultctl + Postgres (prompts for a public domain)
    • Simple — vaultctl + Postgres bound to 127.0.0.1:PORT (prompts for the port; front it with your own reverse proxy)
  4. Generates cryptographic secrets (JWT, peppers, data-encryption key) and writes .env
  5. Pulls the vaultctl + PostgreSQL images and starts all services
  6. Applies database migrations automatically
  7. Waits for the health check and prints your vault URL

The installer is fully interactive — it reads every prompt from your terminal, so it works even when piped through curl ... | bash. Each prompt has a sensible default.

⚠️

macOS: the installer uses Homebrew (opens in a new tab) to install a headless Docker runtime (colima (opens in a new tab) + the docker CLI) when Docker is not already present, then runs colima start for you. Install Homebrew first if you don't have it. Docker Desktop also works if it's already installed.

Unattended installs: export any of these before running to skip the matching prompt — VAULTCTL_INSTALL_DIR, VAULTCTL_MODE (1 full / 2 simple), VAULTCTL_DOMAIN, VAULTCTL_HOST_PORT, VAULTCTL_AUTO_INSTALL_DOCKER=y, VAULTCTL_AUTO_START=y, VAULTCTL_OVERWRITE_ENV=y.


Docker Images

Official multi-arch images (linux/amd64, linux/arm64) are published to both registries on every release. All images are signed with cosign (opens in a new tab) keyless signatures.

docker pull vineethnkrishnan/vaultctl:latest
 
# or pin to a specific version
docker pull vineethnkrishnan/vaultctl:1.1.3

Verify image signature

cosign verify vineethnkrishnan/vaultctl:latest \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'github\.com/vineethkrishnan/vaultctl'

Run directly

docker run -d --name vaultctl \
  -p 8080:8080 \
  -e VAULTCTL_DATABASE_URL="postgres://user:pass@host:5432/vaultctl?sslmode=disable" \
  -e VAULTCTL_JWT_SECRET_CURRENT="$(openssl rand -base64 32)" \
  -e VAULTCTL_SERVER_PEPPER="$(openssl rand -base64 32)" \
  -e VAULTCTL_ENUMERATION_PEPPER="$(openssl rand -base64 32)" \
  -e VAULTCTL_DATA_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
  vineethnkrishnan/vaultctl:latest
⚠️

Always put a TLS-terminating reverse proxy (Caddy, nginx, Traefik) in front. Never expose port 8080 directly to the internet.


Docker Compose

Two compose variants are provided in the repository.

Full Stack — Caddy + vaultctl + PostgreSQL

Includes Caddy for automatic HTTPS via Let's Encrypt.

Clone the repository

git clone https://github.com/vineethkrishnan/vaultctl.git
cd vaultctl

Configure environment

cp .env.example .env

Edit .env and set these required values:

VAULTCTL_ENV=production
VAULTCTL_BASE_URL=https://vault.yourdomain.com
VAULTCTL_DB_PASSWORD=<generate-a-strong-password>

# Generate each with: openssl rand -base64 32
VAULTCTL_JWT_SECRET_CURRENT=<generate>
VAULTCTL_SERVER_PEPPER=<generate>
VAULTCTL_ENUMERATION_PEPPER=<generate>
VAULTCTL_DATA_ENCRYPTION_KEY=<generate>

Generate secrets

for var in JWT_SECRET_CURRENT SERVER_PEPPER ENUMERATION_PEPPER DATA_ENCRYPTION_KEY; do
  echo "VAULTCTL_${var}=$(openssl rand -base64 32)"
done >> .env

Start services

docker compose up -d

Apply database migrations

The server does not migrate on its own. Run this once after the database is healthy (re-run safely after every upgrade):

docker compose exec -T vaultctl /usr/local/bin/vaultctl migrate up

Verify

curl https://vault.yourdomain.com/api/v1/health
# → {"status":"ok"}

Binary

Pre-built binaries are available on GitHub Releases (opens in a new tab) for all major platforms. Each release includes SHA-256 checksums signed with cosign and per-archive SBOMs.

The quickest binary install is the installer's --no-docker mode — it picks the right asset, verifies the checksum, generates secrets, writes config.env, and offers to run migrations:

curl -fsSL https://vaultctl.vinelabs.de/install.sh | bash -s -- --no-docker

You provide your own PostgreSQL and reverse proxy. The manual steps below do the same thing by hand.

Release asset names include the version (e.g. vaultctl_1.4.3_linux_amd64.tar.gz), so resolve the latest tag first:

VERSION=$(curl -fsSL https://api.github.com/repos/vineethkrishnan/vaultctl/releases/latest | grep -m1 '"tag_name"' | cut -d'"' -f4)
 
# amd64 (use _arm64 on ARM hosts)
curl -L "https://github.com/vineethkrishnan/vaultctl/releases/download/${VERSION}/vaultctl_${VERSION#v}_linux_amd64.tar.gz" | tar xz
 
sudo mv vaultctl /usr/local/bin/

Verify checksums

# Download checksums and signature
curl -LO https://github.com/vineethkrishnan/vaultctl/releases/latest/download/checksums.txt
curl -LO https://github.com/vineethkrishnan/vaultctl/releases/latest/download/checksums.txt.sig
curl -LO https://github.com/vineethkrishnan/vaultctl/releases/latest/download/checksums.txt.pem
 
# Verify signature
cosign verify-blob checksums.txt \
  --signature checksums.txt.sig \
  --certificate checksums.txt.pem \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'github\.com/vineethkrishnan/vaultctl'
 
# Verify file integrity
sha256sum -c checksums.txt --ignore-missing

Prerequisites

  • PostgreSQL 16+ running and accessible
  • A reverse proxy (Caddy or nginx) for TLS termination

Run

Configuration is read from environment variables (see Configuration); there is no --config flag. Put your settings in an env file and load it before running:

# Load config (VAULTCTL_DB_*, secrets, VAULTCTL_BASE_URL, ...) into the environment
set -a; . /etc/vaultctl/config.env; set +a
 
# Run database migrations (first run, and after every upgrade)
vaultctl migrate up
 
# Start the server (listens on VAULTCTL_PORT, default :8080)
vaultctl server

Kubernetes

A Helm chart is planned for a future release. For now, use the Docker image directly:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vaultctl
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vaultctl
  template:
    metadata:
      labels:
        app: vaultctl
    spec:
      containers:
        - name: vaultctl
          image: ghcr.io/vineethkrishnan/vaultctl:latest
          ports:
            - containerPort: 8080
          envFrom:
            - secretRef:
                name: vaultctl-secrets
          livenessProbe:
            httpGet:
              path: /api/v1/health
              port: 8080
            initialDelaySeconds: 5
          readinessProbe:
            httpGet:
              path: /api/v1/health
              port: 8080
            initialDelaySeconds: 3
---
apiVersion: v1
kind: Service
metadata:
  name: vaultctl
spec:
  selector:
    app: vaultctl
  ports:
    - port: 80
      targetPort: 8080

Create a Secret with all required environment variables. See the Configuration page for the complete list.

You can use either ghcr.io/vineethkrishnan/vaultctl or vineethnkrishnan/vaultctl (Docker Hub) — both are identical multi-arch images.


Post-Installation

After installation, open your browser and navigate to your server URL.

  1. Click Create Account
  2. Follow the Your First Account guide
  3. Install the Browser Extension
⚠️

Security reminder: Each cryptographic secret (JWT_SECRET, SERVER_PEPPER, ENUMERATION_PEPPER, DATA_ENCRYPTION_KEY) must be unique. Never reuse secrets across deployments. Store them separately from database backups.