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 bin/
bun dev
Open http://localhost:3000, sign up, and create a repository.
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 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.
| 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) |
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).