Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/noelzappy/vaulx/llms.txt

Use this file to discover all available pages before exploring further.

Vaulx ships as a single Alpine-based Docker image with all SQL migrations embedded at build time. There is no migration tool to run separately — on every startup the server applies any pending migrations, configures S3 bucket CORS, seeds the admin account if it does not yet exist, and starts serving traffic. This makes production deployments straightforward: build the image, supply environment variables, and expose port 8080 behind a TLS-terminating reverse proxy.

Docker Compose Deployment

This approach works on any Linux host (VPS, dedicated server, etc.) where Docker and Docker Compose are installed.
1

Set your environment variables

Copy .env.example to .env and fill in every variable with production values. At minimum, change SESSION_SECRET to a cryptographically random string of at least 32 characters before the app is publicly accessible.
cp .env.example .env
# .env — production values
DATABASE_URL=postgres://vaulx:strongpassword@db:5432/vaulx?sslmode=disable
SESSION_SECRET=your-32-plus-char-random-secret-here
PORT=8080

SEED_ADMIN_EMAIL=admin@yourdomain.com
SEED_ADMIN_PASSWORD=StrongAdminPassword1

HETZNER_ACCESS_KEY=your-access-key
HETZNER_SECRET_KEY=your-secret-key
HETZNER_S3_ENDPOINT=https://fsn1.your-objectstorage.com
HETZNER_BUCKET=vaulx

# Injected into the Postgres container
POSTGRES_USER=vaulx
POSTGRES_PASSWORD=strongpassword
POSTGRES_DB=vaulx
2

Build and start the stack in the background

docker compose up --build -d
Docker Compose builds the Vaulx image from the Dockerfile in the repo root, starts the db (Postgres 16 Alpine) and app services, waits for the database health check to pass, then launches the app. Migrations and admin seeding happen automatically on first boot.
3

Add a domain and TLS

Place a reverse proxy (Nginx, Caddy, Traefik, etc.) in front of the Vaulx container to terminate TLS and forward traffic to port 8080. Vaulx itself does not handle TLS.Example Caddyfile snippet:
yourdomain.com {
    reverse_proxy localhost:8080
}
Caddy automatically provisions a Let’s Encrypt certificate. Nginx and Traefik require separate certificate configuration.

Dokploy Deployment

Dokploy is an open-source platform-as-a-service that manages deployments, databases, and domains on your own server. It is the recommended managed deployment path for Vaulx.
1

Create a Postgres service in Dokploy

In the Dokploy dashboard, navigate to Services and create a new Postgres service. Use PostgreSQL 16 and note the generated connection string — you will need it for DATABASE_URL in the next steps.
Dokploy’s internal service networking lets you reference the Postgres container by its internal hostname, so sslmode=disable is safe within the same project.
2

Create an application in Dokploy

Create a new Application with the following settings:
SettingValue
SourceYour Git repository (GitHub, GitLab, or Gitea)
Build typeDockerfile
Dockerfile pathDockerfile (repo root)
Published port8080
Dokploy will clone the repository and build the Docker image using the two-stage Dockerfile (Go builder → Alpine runtime).
3

Set all environment variables

In the Environment tab of your application, add the following variables:
DATABASE_URL=postgres://user:pass@your-pg-host:5432/vaulx?sslmode=disable
SESSION_SECRET=<random 32+ char string>
PORT=8080
SEED_ADMIN_EMAIL=admin@yourdomain.com
SEED_ADMIN_PASSWORD=<strong password>
HETZNER_ACCESS_KEY=<your access key>
HETZNER_SECRET_KEY=<your secret key>
HETZNER_S3_ENDPOINT=https://fsn1.your-objectstorage.com
HETZNER_BUCKET=<your bucket name>
4

Deploy the application

Trigger a deploy from the Dokploy dashboard (or push to your linked branch). Dokploy builds the image and starts the container. On first start the app will:
  1. Run all pending database migrations automatically
  2. Connect to the database and S3 bucket, then apply the bucket CORS policy
  3. Seed the admin account using SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD if the account does not yet exist
  4. Start serving HTTP on $PORT
Check the Logs tab to confirm migrations completed and the server is listening.
5

Add a domain and enable HTTPS

In the Domains tab of your Dokploy application, add your domain and enable HTTPS. Dokploy provisions a Let’s Encrypt certificate and routes traffic to port 8080 automatically.HTTPS is required — the session cookie has Secure: true, so the login flow will not work over plain HTTP in production.

Environment Variables Reference

VariableRequiredDefaultDescription
DATABASE_URLYesPostgres connection string, e.g. postgres://user:pass@host:5432/db?sslmode=disable
SESSION_SECRETYesRandom string ≥ 32 chars used to sign and verify session cookies
PORTNo8080HTTP port the server listens on
SEED_ADMIN_EMAILYesEmail address for the admin account seeded on first boot
SEED_ADMIN_PASSWORDYesPassword for the seeded admin account (min 8 chars, at least one digit)
HETZNER_ACCESS_KEYYesHetzner Object Storage access key (S3-compatible)
HETZNER_SECRET_KEYYesHetzner Object Storage secret key
HETZNER_S3_ENDPOINTYeshttps://fsn1.your-objectstorage.comBase URL of the S3-compatible storage endpoint
HETZNER_BUCKETYesvaulxBucket name where uploaded files are stored
Change SESSION_SECRET to a strong, random value before exposing Vaulx publicly. The default value in .env.example and docker-compose.yml is a placeholder. Any session signed with the placeholder secret can be forged.
The session cookie is configured with Secure: true. Browsers will refuse to send this cookie over plain HTTP, which means the login flow will silently fail unless Vaulx is served over HTTPS. Always place a TLS-terminating reverse proxy (Caddy, Nginx, Traefik) or enable HTTPS in Dokploy before directing real users to your deployment.

First-Boot Behavior

Every time the Vaulx container starts it runs the following sequence before accepting HTTP traffic:
  1. Migrationsgolang-migrate applies any pending .sql files from the migrations/ directory, which is embedded directly into the binary at build time via embed.FS. No external migration tooling is needed on the host.
  2. Database & storage connect — The server opens the PostgreSQL connection pool and connects to the configured S3-compatible storage bucket.
  3. Storage CORS — The server sets the CORS policy on the configured S3 bucket to allow browser-initiated presigned uploads from your domain.
  4. Admin seed — If no user with SEED_ADMIN_EMAIL exists in the database, the server creates an admin-role account with the provided email and password. Subsequent restarts skip this step if the account already exists.
  5. HTTP listener — The server starts accepting requests on $PORT.

Docker Image Details

The Dockerfile uses a two-stage build:
StageBase imagePurpose
buildergolang:1.25-alpineInstalls templ, downloads Go modules, runs templ generate, compiles the server binary with CGO_ENABLED=0
Runtimealpine:3.20Copies only the compiled server binary and the web/static/ directory; adds ca-certificates and tzdata
The resulting image is minimal — no Go toolchain, no source code. All SQL migration files are embedded into the server binary at compile time, so the runtime image contains everything needed to deploy and upgrade the schema with no extra tooling.
# Final stage — what actually runs in production
FROM alpine:3.20
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/server .
COPY --from=builder /app/web/static ./web/static
EXPOSE 8080
CMD ["./server"]

Build docs developers (and LLMs) love