Git hosting and a container registry in one Rust binary (axum + Astro)
Clone ▾
| 131 B | |
| 253 B | |
| 538 B | |
| 124 KB | |
| 322 B | |
| 2.2 KB | |
| 570 B | |
| 2.6 KB | |
| 4.2 KB |
irongit
Git hosting and a container registry in one binary, built on the astrum
template (axum + Astro). Users and organizations share one namespace; repos
and images are public or private independently; profiles have a contribution
heatmap; ig is the CLI.
What runs where
| Piece | Where it lives |
|---|---|
| Accounts, permissions, metadata, sessions, tokens | Postgres |
| Git repositories (bare) | Server disk, DATA_DIR/repos/{id}.git (by id, so renames never move files) |
| Git LFS objects | R2 lfs/, uploaded and downloaded by clients through presigned URLs |
| Image layers and configs | R2 registry/blobs/, pulls are 307 redirects to presigned URLs |
| Image manifests and tags | Postgres |
| Avatars | R2 avatars/ |
| CLI releases | R2 releases/cli/ |
| Backups (git bundles + pg_dump) | R2 backups/ |
| Logs | DATA_DIR/logs/ (server.*.log, frontend.*.log, hooks.*.log) |
Git transport and browsing go through the system git binary (it must be on
PATH). Pushes run a pre-receive hook (irongit hook pre-receive, the same
binary) that rejects files over MAX_FILE_MB and pushes over the owner's
quota, pointing people at Git LFS.
Develop
Requirements: Rust, bun, git, watchexec, Postgres (local dev uses the
container on 5432, database irongit).
./dev.sh # http://localhost:7878, git SSH on 2222
Configuration is .env at the repo root; every variable is described in
secrets.md (gitignored). The first account registered becomes site admin;
irongit admin promote <user> makes more.
cargo test -p irongit # unit tests
cargo run -p irongit -- admin check-storage # verify R2 credentials
Build
./build.sh
# target/release/irongit server (x86_64 Linux)
# target/x86_64-unknown-linux-musl/release/ig CLI, static
Publish a CLI build so install.sh and ig upgrade serve it:
irongit admin publish-cli target/x86_64-unknown-linux-musl/release/ig --version 0.1.0
The ig CLI
curl -fsSL http://localhost:7878/install.sh | sh # installs ig and docker-credential-ig
ig login # approve in the browser; sets up git and docker helpers
ig repo create api --private
ig repo clone you/api
docker push localhost:7878/you/api:1.0
ig image visibility you/api public
ig ssh-key add # ~/.ssh/id_ed25519.pub by default
ig upgrade
Full reference at /docs/cli; git, SSH and LFS at /docs/git; the
registry at /docs/registry.
Tests
cargo test --workspace # unit tests (server, CLI, shared types)
scripts/e2e/ssh-lfs.sh # SSH, LFS end to end against a running server
scripts/e2e/backup.sh # backup to R2 and restore every bundle
Layout
backend/
migrations/ Postgres schema
src/main.rs startup, subcommands (serve, hook, admin)
src/auth.rs sessions, personal access tokens, extractors
src/perm.rs every access decision (repos, images, orgs)
src/storage.rs R2 over the S3 API (presigned URLs, multipart)
src/git.rs git CLI wrapper used by the web UI
src/git_http.rs smart HTTP (v0, v1, v2)
src/transport.rs spawning upload-pack/receive-pack with hooks and limits
src/hook.rs pre-receive size and quota checks
src/push.rs post-push: events, default branch, heatmap contributions
src/ssh.rs built-in SSH server
src/lfs.rs Git LFS batch API
src/registry/ OCI distribution API (docker push/pull)
src/api/ JSON API for the CLI, device login, CLI releases
src/backup.rs scheduled backups to R2
src/web/ server-rendered pages (maud) poured into the Astro shell
cli/ the ig CLI
shared/ types shared by the API and the CLI
frontend/ Astro: landing, docs, the page shell, CSS and client JS
Deploying
Repos need a persistent disk, so the server runs on a VM, not serverless.
Put the git and registry hostname on Cloudflare DNS-only (grey cloud):
the proxy caps request bodies at 100 MB, which breaks large docker push
layers and big HTTPS git pushes. Set SECRET_KEY, PUBLIC_URL,
R2_BUCKET=irongit and the SMTP and Google variables in production.