Backend — Pro edition¶
Pro is Community plus automation: instead of uploading WireGuard® .conf
files by hand, the backend creates and removes VPN peers itself through the
integrated WireGuard® server. It also adds MFA
session gating, traffic dashboards, and appliance fleet management.
The Pro backend is a pre-built container image — no source checkout, no build step. Pro features switch on once a license key is loaded; without one, the same image simply runs as Community. Get a free 14-day trial from valenius.com.
Pick an installation method¶
There are three ways to get the Pro backend running. All three use the same pre-built image from GHCR — pick whichever matches how you work. You only need to follow one of them.
| Method | What it needs | Best for |
|---|---|---|
| Shell scripts (recommended) | SSH access, git | The fastest path — two commands to install, one to upgrade |
| Plain Docker / shell | SSH access, comfortable copy-pasting commands | Full control — a custom deploy path, an existing Postgres, non-default volumes |
| Portainer | A Portainer instance (or install one as step 0) | No shell commands at all after the initial setup — install and upgrade from a web UI |
Prerequisites¶
- A Linux server with Docker Engine and the Docker Compose plugin — see Prerequisites.
- An HTTPS reverse proxy (Nginx Proxy Manager, Caddy, Traefik, …) and a domain name pointed at the server. You can skip this for a quick local test, but sign-in cookies and SSO need real HTTPS in front.
git— only needed for the shell scripts method. Portainer and plain Docker don't need it.- A Pro license key or trial key (you can also add it later).
Install¶
install.sh (Community installer) and its companion proupgrade.sh
together give you a running Pro stack in two commands. install.sh
creates the base stack, then proupgrade.sh immediately swaps the
backend for the pre-built Pro image.
1. Clone the repository¶
2. Install Community, then upgrade to Pro¶
install.sh prompts for an admin email and the host port (defaults:
admin@example.com, 9001). For a non-interactive run:
proupgrade.sh then backs up the database, swaps the backend image to
the pre-built Pro image, and prompts for a license key. Blank is fine —
you can add it later by re-running proupgrade.sh or by editing
valenius/.env.
No chmod needed
install.sh, update.sh, and proupgrade.sh are already marked
executable in the repository — run them straight after cloning.
Where the files end up
install.sh writes into a valenius/ folder inside the directory
you run it from. Since the cloned repo is also named valenius, your
.env lands at ~/valenius/valenius/.env. ls -a (not plain ls)
shows dotfiles.
Once it prints your credentials, skip ahead to Put your reverse proxy in front.
If you'd rather see and control each step yourself — a custom deploy path, an existing Postgres, non-default volumes — follow this instead.
1. Create a deployment folder¶
2. Create the .env file¶
Create a file named .env with this content, replacing every change-me
value with a strong random one (generate them with openssl rand -hex 32):
# .env
IMAGE_TAG=ghcr.io/valeniusvpn/valenius-pro:latest
DB_PASSWORD=change-me-a-strong-random-password
# Shared secret between the backend and every desktop client it issues.
WGT_API_KEY=change-me-a-random-key
# Bootstrap admin -- your first login. Also your emergency fallback later.
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=change-me-a-strong-random-password
# Host port the backend listens on (your reverse proxy forwards to this).
BACKEND_PORT=9001
# Leave blank to run as Community until you set a key here and restart.
VALENIUS_LICENSE_KEY=
Special characters are safe here
Values in .env are not $-expanded by Docker Compose, so any random
key — including ones containing $ — works without escaping.
3. Create the docker-compose.yml file¶
Create a file named docker-compose.yml next to .env, with exactly
this content:
services:
db:
container_name: valenius-database
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: valenius
POSTGRES_USER: valenius
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- valenius-database-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U valenius -d valenius"]
interval: 10s
timeout: 5s
retries: 5
backend:
container_name: valenius-backend
image: ${IMAGE_TAG}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_HTTP_PORTS: ""
ConnectionStrings__DefaultConnection: >-
Host=db;Port=5432;Database=valenius;Username=valenius;Password=${DB_PASSWORD}
DataProtection__KeysPath: /app/keys
Valenius__ApiKey: ${WGT_API_KEY}
Valenius__AdminEmail: ${ADMIN_EMAIL:-}
Valenius__AdminPassword: ${ADMIN_PASSWORD:-}
Valenius__ManifestPath: /app/data/versions.json
Valenius__DownloadsPath: /app/data/downloads
VALENIUS_LICENSE_KEY: ${VALENIUS_LICENSE_KEY:-}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./data:/app/data
- valenius-keystore:/app/keys
ports:
- "${BACKEND_PORT}:8080"
volumes:
valenius-database-data:
valenius-keystore:
4. Start the stack¶
Wait until the log shows a line beginning with Now listening on:, then
press Ctrl+C to leave the logs (the stack keeps running).
Deploy using the pre-built Pro image — Portainer pulls it from GHCR and starts the stack for you. Upgrading later is a single button click.
0. Install Portainer (skip if you already run it)¶
docker volume create portainer_data
docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
Open https://<server-ip>:9443, create the Portainer admin account, and
finish its setup wizard (choose "Get Started" / the local environment).
1. Create the stack¶
In Portainer, go to Stacks → Add stack and fill in:
| Field | Value |
|---|---|
| Name | valenius |
| Build method | Web editor |
Paste the following into the compose editor:
services:
db:
container_name: valenius-database
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: valenius
POSTGRES_USER: valenius
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- valenius-database-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U valenius -d valenius"]
interval: 10s
timeout: 5s
retries: 5
backend:
container_name: valenius-backend
image: ${IMAGE_TAG}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_HTTP_PORTS: ""
ConnectionStrings__DefaultConnection: >-
Host=db;Port=5432;Database=valenius;Username=valenius;Password=${DB_PASSWORD}
DataProtection__KeysPath: /app/keys
Valenius__ApiKey: ${WGT_API_KEY}
Valenius__AdminEmail: ${ADMIN_EMAIL:-}
Valenius__AdminPassword: ${ADMIN_PASSWORD:-}
Valenius__ManifestPath: /app/data/versions.json
Valenius__DownloadsPath: /app/data/downloads
VALENIUS_LICENSE_KEY: ${VALENIUS_LICENSE_KEY:-}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./data:/app/data
- valenius-keystore:/app/keys
ports:
- "${BACKEND_PORT}:8080"
volumes:
valenius-database-data:
valenius-keystore:
2. Set the environment variables¶
Still on the "Add stack" page, scroll to Environment variables and add these (Portainer also lets you paste them all at once via "Load variables from .env file" if you prefer):
| Name | Value |
|---|---|
IMAGE_TAG |
ghcr.io/valeniusvpn/valenius-pro:latest |
DB_PASSWORD |
a strong random password, e.g. from openssl rand -hex 32 |
WGT_API_KEY |
a strong random key |
ADMIN_EMAIL |
your login email, e.g. admin@example.com |
ADMIN_PASSWORD |
a strong random password |
BACKEND_PORT |
the host port to expose, e.g. 9001 |
VALENIUS_LICENSE_KEY |
your license or trial key (leave blank to add later) |
DB_PASSWORD, WGT_API_KEY, and BACKEND_PORT are required
The stack will fail to deploy without these three. The others have safe defaults or can be left blank.
3. Deploy the stack¶
Click Deploy the stack. Portainer pulls the Pro image from GHCR and starts both containers. Watch progress under Stacks → valenius → Logs.
Once both containers show running (and valenius-database shows
healthy) under Containers, skip ahead to
Put your reverse proxy in front.
Put your reverse proxy in front¶
In your reverse proxy, create an HTTPS host (for example
vpn.example.com) that forwards to http://<server-ip>:9001 (or whatever
you set as BACKEND_PORT).
Add your license and sign in¶
- If you haven't set a license key yet, add it now — in
.env(shell/plain Docker methods) or in the stack's environment variables (Portainer) — and restart the stack. (Skipping this runs the same image as Community — you can add the key any time later.) - Open
https://vpn.example.com/and sign in withADMIN_EMAILandADMIN_PASSWORD. The License card on the Overview page shows the key's status.
There is no license field in the admin UI
The license key lives in .env / the stack's environment variables only.
To change it later, edit the key there and redeploy.
Verify it works¶
- Both containers show running —
docker compose ps(shell methods) or the Containers list in Portainer — and the database container shows healthy. - The admin panel opens at your HTTPS hostname and you can sign in.
- After adding the license, the Pro badge and the extra menu items (Server, MFA, Appliances) appear in the admin panel.
Next: set up the integrated WireGuard® server so clients get their VPN peers automatically — then continue with First steps after install.
Upgrading later (already on Pro)¶
proupgrade.sh backs up the database, pulls the latest Pro image, and
restarts the stack. Re-run it any time you want to update.
update.sh works too, for routine updates
update.sh (repo root) auto-detects that this stack is on Pro and
pulls the latest published image instead of rebuilding Community —
one command works for either edition. Reach for proupgrade.sh
instead when you need to change the license key or want a database
backup taken as part of the update.
docker compose up -d recreates the backend container automatically
since the pulled image gets a new ID under the same tag — you don't need
--force-recreate.
Stacks → valenius → Pull and redeploy.
Portainer re-fetches the image at the configured tag and recreates the backend container — one click, no shell access needed.
Database upgrades happen automatically at startup in all three cases — there is no separate migration step, and your data is kept.
Already running Community? Upgrade in place¶
You don't need to set up a new server. Switching your existing Community install to Pro reuses the same database and the same client API key — your customers and clients keep working without being reconfigured.
Back up first, and keep the same stack name
Take a database backup before you start
(docker compose exec -T db pg_dump -U valenius valenius > backup.sql),
and make sure whichever method you use below reuses your existing
deployment folder / Portainer stack name — a new one gets fresh, empty
volumes instead of upgrading your real data.
If Community was installed with install.sh, run its companion
proupgrade.sh from the same repo clone (not the valenius/ folder
it created — one level up, next to install.sh itself):
It backs up the database, swaps the backend for the pre-built Pro image,
and prompts for a license key. There's no license field in the admin
UI — blank is fine, but "add it later" means editing
valenius/.env (VALENIUS_LICENSE_KEY=...) and running
docker compose up -d, or just re-running proupgrade.sh.
Use update.sh for routine updates afterwards
update.sh (same repo root) detects that this stack is now on Pro
and pulls the latest published Pro image instead of rebuilding
Community — one command works for either edition. Re-run
proupgrade.sh only when you need to change the license key or
want a database backup taken as part of the update.
In your existing deploy folder (not a new one), change the backend service's image and add a license key:
# add to your existing .env
IMAGE_TAG=ghcr.io/valeniusvpn/valenius-pro:latest
VALENIUS_LICENSE_KEY=your-key-here
Then:
Open your existing stack (Stacks → valenius → Editor) and replace its
content with the compose file from the
Portainer install tab above. Keep your existing DB_PASSWORD
/ ADMIN_EMAIL / ADMIN_PASSWORD / BACKEND_PORT variables, add
IMAGE_TAG=ghcr.io/valeniusvpn/valenius-pro:latest and
VALENIUS_LICENSE_KEY, then Update the stack.
If your Portainer version won't let you switch a git-linked stack's source in place, delete it with "Remove associated volumes" unchecked and recreate it under the exact same name — the name is what lets Compose find your existing volumes again.
Full detail (why this is safe, what stays the same): Self-hosting: Pro edition → Upgrading from Community.
Common problems¶
The Pro menu items don't appear.
The license key is missing, mistyped, or expired. The License card on
the Overview page shows the validation result; the backend log
(docker compose logs backend) states the reason if the key was rejected.
Without a valid key the backend runs as Community; nothing is broken.
docker compose up (or the Portainer deploy) fails with "port is already
allocated".
Another service is using the port. Pick a different BACKEND_PORT in
.env (or the stack's environment variables) and redeploy.
The image can't be pulled.
The image is public and needs no docker login — if the pull fails, check
the server's internet access and that IMAGE_TAG is exactly
ghcr.io/valeniusvpn/valenius-pro:latest.
I can sign in, but only when I access the port directly — through the proxy I land back on the sign-in page. Your proxy isn't forwarding HTTPS properly. Make sure the proxy host uses HTTPS with a valid certificate and forwards to the backend port over plain HTTP. Learn more →
I forgot the admin password.
Set a new ADMIN_PASSWORD in .env (or the stack's environment variables)
and run docker compose up -d again — the bootstrap admin's password is
updated on every container start.
The backend log shows database connection errors at first start.
Usually just timing — the backend waits for the database health check, so
give it a minute. If it keeps failing, check DB_PASSWORD for stray quotes
or spaces.
I changed DB_PASSWORD and now the backend can't connect.
The database keeps the password it was first created with — changing
DB_PASSWORD later only changes what the backend sends. Either change it
back, or reset the database volume (docker compose down -v, or delete
valenius-database-data in Portainer — this deletes all data) and
start fresh.