Gitly is a lightweight, self-hosted GitHub/GitLab alternative written in V.
It provides:
- Multiple users, personal and organization repositories, and public/private visibility
- Git clone, fetch, and push over HTTP(S) and OpenSSH
- Repository browsing, syntax highlighting, Markdown rendering, language statistics, and a “Top files” view
- Issues with assignees, merge requests with reviews, required approvals, squash/merge support, discussions, projects, milestones, releases, stars, and watches
- Reporter/Developer/Maintainer project roles and protected branches with wildcard rules, push/merge controls, deletion protection, and force-push rejection
- Webhooks, scoped/expiring personal access tokens, two-factor authentication, security logs, and optional CI integration
- Repository forks with upstream synchronization and cross-fork merge requests
- Scheduled/manual pull and push mirrors over HTTPS or SSH, with encrypted credentials
- SQLite or PostgreSQL storage and compiled-in templates
Gitly is beta software. It is a lightweight forge rather than a complete GitLab distribution; advanced CI/CD, registries, enterprise identity, security scanning, LFS, and a repository wiki remain outside the implemented foundation.
The current GitLab capability comparison and the remaining implementation sequence are documented in docs/gitlab-parity.md. Gitly is intentionally described as a lightweight alternative, not as complete GitLab parity while major platform areas remain outstanding.
A recent V compiler, Git, and a C compiler are required. Install sassc to compile the stylesheet on a clean checkout; the build intentionally does not download unpinned generated assets. The Markdown module is included in this repository.
Build with:
v run build.vsh
./gitlyGitly builds against PostgreSQL by default. Create the default local role and database with:
v run setup_db.vshTo use SQLite instead:
v -d sqlite -o gitly .
./gitlyThe SQLite database defaults to gitly.sqlite; change it with sqlite.path in config.json or GITLY_SQLITE_PATH. Repository, archive, and avatar storage can be overridden with GITLY_REPO_STORAGE_PATH, GITLY_ARCHIVE_PATH, and GITLY_AVATARS_PATH. PostgreSQL accepts the pg configuration block, GITLY_DB_* variables, the usual PG* variables, or DATABASE_URL.
System libraries:
- SQLite:
libsqlite3-devon Ubuntu/Debian - PostgreSQL:
libpq-devon Ubuntu/Debian orbrew install libpqon macOS - Stylesheet compilation:
sassc
Review config.json before deployment:
- Set
hostnameto the public host. - Set
cookie_securetotruewhenever the site is served over HTTPS. - Set
ci_secretto a long random value if CI is enabled, and configure the same secret in the CI service. CI callbacks are rejected when this secret is empty.GITLY_CI_SECRETcan override the Gitly-side value. - Gitly derives the CI status callback from
hostnameandcookie_secure. If the CI service cannot reach that URL, setci_callback_url(orGITLY_CI_CALLBACK_URL) to the complete externally reachable callback endpoint, for examplehttps://git.example.com/api/v1/ci/status. - Set
GITLY_STORAGE_SECRETto a long random value before saving credentialed repository mirrors. Mirror passwords, access tokens, and SSH private keys fail closed when encryption is not configured. - Keep the CI service and database on trusted networks and terminate HTTPS at Gitly or a reverse proxy.
- Treat CI jobs as untrusted code. Run the separate
gitly_ciservice on an isolated runner host/VM (or an equivalent container sandbox) with a minimal allowlisted environment and no access to Gitly's database credentials, storage secret, repository storage, or host filesystem beyond its disposable workspace. Do not co-locate the current shell runner with the Gitly service in production.
Gitly uses the host OpenSSH server instead of embedding a second SSH daemon. Configure a dedicated SSH account and set:
export GITLY_SSH_ENABLED=true
export GITLY_SSH_HOSTNAME=git.example.com
export GITLY_SSH_PORT=22
export GITLY_SSH_USER=git
export GITLY_SSH_AUTHORIZED_KEYS_PATH=/home/git/.ssh/authorized_keysThe Gitly process must be able to atomically update that file. It preserves entries outside its managed block and installs restricted forced commands for active authentication and deploy keys. See docs/ssh.md for the OpenSSH setup and security model.
Fork and mirror behavior, including private-source visibility, divergence handling, credential encryption, SSH host-key pinning, and internal-host allowlisting, is documented in docs/repository-forks-mirrors.md.
Run the tracked test suite with SQLite support:
v -d sqlite test $(git ls-files '*_test.v')The end-to-end first-run check uses a process-specific port and isolated temporary database/storage, then clones a public repository:
v run tests/first_run.v- Small memory and deployment footprint
- Useful repository navigation without requiring JavaScript
- CI-aware releases whose source tree remains identifiable
- Explicit repository-transfer acceptance, so another user cannot force content into an account namespace