vectrum.cc · A self-hosted, multi-user GitHub built with Next.js. Repositories are real bare git repositories on disk, and you push to them with plain git over SSH or HTTPS.
docker push <host>/<owner>/<repo> (or <host>/<owner>/<repo>/<name> for more images per repository) with the same visibility, collaborators and API tokens as the code. Multi-platform images, cross-repository blob mounts, chunked uploads and the referrers API (signatures, SBOMs) are supported. Images are listed under the repository's Packages tab, where writers can delete versionsC new issue, J/K move, Enter open, S/P/A/L set status, priority, assignee, labelsRequires Node.js 22.5+ (for the built-in node:sqlite) and git on the PATH.
bun install
bun dev
Open http://localhost:3000, sign up, and create a repository.
~/.ssh/id_ed25519.pub) under Settings → SSH keys.ssh -T -p 2222 git@localhostgit remote add origin ssh://git@localhost:2222/<you>/<repo>.git
git push -u origin main
Generate a token under Settings → API tokens and use it as the password when git asks (any username works):
git clone http://localhost:3000/<you>/<repo>.git
# Username: <you> Password: vec_…
Anonymous users can clone public repositories without a token.
Log in with an API token as the password (read & write to push), then tag and push images under a repository's name. Image names are lowercase:
docker login localhost:3000 -u <you> # Password: vec_…
docker build -t localhost:3000/<you>/<repo>:latest .
docker push localhost:3000/<you>/<repo>:latest
docker push localhost:3000/<you>/<repo>/worker:1.0 # more images in one repository
Anyone who can read the repository can pull; anonymous users can pull images of
public repositories. The repository has to exist before you push. Docker only
talks plain HTTP to localhost; anywhere else, serve Vectrum over HTTPS (or add
the host to Docker's insecure-registries). A reverse proxy in front of
Vectrum must pass large request bodies through (e.g. nginx client_max_body_size 0;).
| Variable | Default | Purpose |
|---|---|---|
DATA_ROOT | ./data | Where repositories, the database and the SSH host key live |
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 |
Data layout:
data/
app.db users, sessions, keys, tokens, repos, issues (SQLite)
ssh_host_ed25519_key generated on first start
repos/<owner>/<repo>.git bare git repositories
registry/blobs/sha256/… container image layers, shared by all repositories
registry/uploads/ image uploads in progress
Image manifests and tags are stored in the database. 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).
Secure when X-Forwarded-Proto: https is set.nosniff and a sandboxing CSP, so repository HTML/SVG can't run scripts in the app's origin.lib/db.ts: SQLite schema and connectionlib/users.ts, lib/auth.ts: accounts, password hashing, sessionslib/repos.ts: repository records, permissions (repoAccess), collaboratorslib/issues.ts, lib/issue-meta.ts: issue tracker data and its shared vocabulary (statuses, priorities); UI in components/issues/lib/keys.ts, lib/tokens.ts: SSH keys and API tokenslib/git.ts, lib/git-write.ts: reading from and committing to bare repositorieslib/git-http.ts, lib/ssh-server.ts: git transports (started from instrumentation.ts)lib/registry.ts, lib/registry-http.ts: container registry storage and the OCI distribution API, served under /v2app/actions/*: server actions, each re-checking the session and permissions