Requires Node.js 22+ (24.7+ for post-quantum SSH key exchange) and git on the PATH.
bun install # also downloads the PocketBase server into apps/web/bin/
bun dev
Open http://localhost:3000, sign up, and create a repository.
The web app is apps/web; paths below are relative to it. Commands run from the repository root or from apps/web.
The data lives in PocketBase. Vectrum starts bin/pocketbase itself on 127.0.0.1:8090, with its data in data/pb_data and the schema from pb_migrations/; its superuser login is generated into data/pocketbase-superuser.json (also good for the PocketBase dashboard at http://127.0.0.1:8090/_/). Run bun run pocketbase:install if the binary is missing. To use a PocketBase server of your own instead, set PB_URL and the superuser credentials; it needs the migrations in pb_migrations/ (pocketbase serve --migrationsDir pb_migrations).
docker compose up -d --build
The image runs the web UI on port 3000 and the SSH server on port 2222, with all data in the vectrum-data volume (mounted at /data). Settings go under environment: in docker-compose.yml (see Configuration). To close registration, set SIGNUPS_DISABLED: "1" before you deploy, and create accounts from the command line instead:
docker compose exec -it vectrum node scripts/create-user.mjs <username> <email> [name]
It asks for the password (or reads VECTRUM_PASSWORD). Outside Docker, run node scripts/create-user.mjs the same way from apps/web while Vectrum runs. Clone URLs show SSH_PORT, so publish SSH on the same port on the host, and set SSH_HOST to your domain. Put a TLS reverse proxy in front of port 3000 if the server is reachable from the internet, and set PUBLIC_URL to its address.
To build the image without Compose, run docker build -f apps/web/Dockerfile -t vectrum . from the repository root.
| Variable | Default | Purpose |
|---|---|---|
PUBLIC_URL | request host | Canonical address, e.g. https://vectrum.cc. Used for clone URLs; https also makes session cookies Secure. Set it in production |
SECURE_COOKIES | — | Set to 1 to mark session cookies Secure without PUBLIC_URL |
DATA_ROOT | ./data | Where repositories, the database and the SSH host key live |
PB_PORT | 8090 | Port of the PocketBase server Vectrum runs |
PB_URL | — | Use this PocketBase server instead of running one |
PB_SUPERUSER_EMAIL, PB_SUPERUSER_PASSWORD | generated | Superuser login; required with PB_URL |
PB_BINARY | bin/pocketbase | PocketBase executable to run |
SSH_PORT | 2222 | Port of the built-in SSH server |
SSH_LISTEN_HOST | 0.0.0.0 | Interface the SSH server binds to |
SSH_HOST | request host | Host name shown in SSH clone URLs, e.g. vectrum.cc |
SSH_DISABLED | — | Set to 1 to not start the SSH server |
SIGNUPS_DISABLED | — | Set to 1 to close registration (add users with scripts/create-user.mjs) |
WEBHOOKS_ALLOW_PRIVATE | — | Set to 1 to let webhooks reach private and local addresses (a trusted, single-team server) |
SMTP_HOST | — | SMTP server to send email through (see Email); without it, Vectrum sends none |
SMTP_PORT | 587 | Its port |
SMTP_SECURE | on for port 465 | 1 for TLS from the start (port 465); otherwise the connection upgrades with STARTTLS when the server offers it |
SMTP_USER, SMTP_PASSWORD | — | Login for the SMTP server, if it needs one |
MAIL_FROM | SMTP_USER | Sender, e.g. Vectrum <noreply@vectrum.cc> |
With SMTP_HOST set, Vectrum sends email: a link to confirm the address after signing up or changing it, a link to reset a forgotten password (Forgot your password? on the sign-in page), organization invitations, being added as a collaborator, and notices when an account's password or email address changes. Notifications only go to confirmed addresses. Set PUBLIC_URL too, as the links in emails point there.
Outside Docker, put the settings in apps/web/.env (see apps/web/.env.example), which Next.js reads on start:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=vectrum@example.com
SMTP_PASSWORD=…
MAIL_FROM="Vectrum <vectrum@example.com>"
PUBLIC_URL=https://vectrum.example.com
With Docker Compose, add them under environment:, or put them in a .env file next to docker-compose.yml and list it under env_file:. Failures to send are logged.
Data layout:
data/
pb_data/ PocketBase: users, keys, tokens, repos, issues, releases, workflow runs and logs
pocketbase-superuser.json the generated PocketBase superuser login
ssh_host_ed25519_key generated on first start
hooks/ server-side git hooks for every push, rewritten on start, and the socket they ask the server on
repos/<owner>/<repo>.git bare git repositories
wikis/<repo>.git each repository's wiki, named by its record id
registry.db container image manifests, tags and blob records (SQLite)
registry/blobs/sha256/… container image layers, shared by all repositories
registry/uploads/ image uploads in progress
releases/<repo>/<release>/… release files, named by their record id
The registry keeps its records in SQLite rather than PocketBase so that storing a blob and pruning unused ones never interleave. A blob is deleted once no manifest of any repository references it (blobs pushed in the last 24 hours are kept so pushes in progress don't lose their layers).