Hearthforge 

A self-hosted git forge designed to host your own repositories, but allow others to interact with them via issues and patch proposals. Frontend works without any JS at all enabled, just required for WebAuthn (with graceful fallback to password-only).
Features
- Repository browser — file tree, directly edit single files, blob view, commit log, markdown rendering, media previews
- Issues — create, comment, react
- Patches — submit git .patch files for review & comments. Admin can merge applicable patches directly into the repository.
- Templates — issue/patch templates
- Releases — releases with source archives, extra uploaded assets, and optional tag creation
- SSH push/pull — built-in SSH server, no external git daemon needed
- Auth — password login or passkeys (WebAuthn/FIDO2)
- Commit signing — merged patches and filed edited through the UI are automatically signed and verification badges are shown in the commit list view
- Optional registration — others can create accounts to file issues and patches; can be disabled, or put in queue mode where the admin manually approves new accounts.
Stack
- Bun — runtime and package manager
- ElysiaJS — HTTP framework
- SQLite — single-file database via Kysely
- Server-side JSX via @kitajs/html (no client-side framework)
- Shiki — syntax highlighting
Running
bun install
bun run db:init # creates database and admin account
bun run start # http://localhost:3000, SSH on port 2222
Default admin credentials: admin / changeme
Manual repository interactions
Existing Repositories can be copied manually to the data/repos directory.
Non-bare repos are automatically converted to bare repos on startup, discarding uncomitted changes and worktrees.
Pushing directly to the repositories (i.e. not through the bundled http/ssh endpoints) should generally works as well.
Docker / Podman
Container image and compose file are provided.
docker compose up # or
podman compose up
Configuration
All settings are environment variables:
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port |
SSH_PORT |
2222 |
SSH port |
DATA_DIR |
./data |
Repos, database, uploads |
ADMIN_PASSWORD |
changeme |
Initial admin password |
OWNER_DISPLAY_NAME |
Admin |
Display name for the owner |
BASE_URL |
http://localhost:3000 |
Used in clone URLs and links |
REGISTRATION_TYPE |
enabled |
Registration mode: enabled, disabled, or queue (requires admin approval) |
REGISTER_QUESTION |
(empty) | Question shown on the registration form when REGISTRATION_TYPE=queue |
MAX_UPLOAD_BYTES |
10485760 |
Max request body size (any uploads/requests) |
MAX_USER_UPLOAD_BYTES |
2097152 |
Max request body size (user uploads) |
INLINE_MAX_BYTES |
524288 |
Max file size to render inline in the file view |
SSH_DISABLED |
0 |
Disable the embedded SSH-server |
TRUSTED_PROXY |
0 |
Trust X-Forwarded-For |
RATE_LIMIT_DISABLED |
0 |
Set to 1 to disable rate limiting |
HIGHLIGHT_WORKERS |
4 |
Number of syntax highlighting workers* |
COMMITTER_NAME |
$OWNER_DISPLAY_NAME |
Git committer name used when merging patches or editing files through the UI |
COMMITTER_EMAIL |
$OWNER_DISPLAY_NAME@<hostname> |
same as above but for email |
* More workers mean more CPU cores can be used to parallelize highlighting of files. Because of the language grammars, which can't be shared across workers, the memory usage per worker is quite high, at about 200MB. So be careful when increasing this.
SSH access
Add your public key under Settings → SSH keys, then clone with:
git clone ssh://git@localhost:{SSH_PORT}/{REPO_NAME}
Pushing is also supported for the admin.
Development
bun run dev # watch mode
bun run lint # Biome lint
bun run format # Biome format
bun run test # Run E2E and unit tests (don't use bun test directly, it doesn't respect the timeout)
Roadmap
- Use git-bug for issue tracking instead of custom implementation
- Issue labels
- more repository manipulation through the UI, e.g. file/directory/branch creation, renaming and deletion