Gilti is a tiny web UI for Git that can function as either a read-only showcase
or a Git SSH server by integrating with the system's sshd. It is designed for
open-source projects and small teams that want to break free from major hosting
platforms but aren't ready to host complex services like Forgejo.
Gilti is a young project. Mirror your repositories and create backups.
- Git fetch is available anonymously over smart HTTP; authenticated fetch and
push use SSH public-key authentication through
gilti shell. - Every configured key has read/write access to every repository and may create a repository by pushing to its name for the first time.
- Repository browsing, archives, LFS downloads, and smart HTTP fetches are anonymous and read-only; every repository is publicly visible.
- Password authentication, shells, forwarding, and tunnels are disabled; content filters are not supported. Optional unauthenticated HTTP writes must be enabled explicitly.
- Gilti is a single-replica service backed by one POSIX persistent volume. It is not an HA system.
The image contains a default TOML configuration. SSH-enabled starts also require
an authorized_keys file:
docker pull ghcr.io/dimidiumlabs/gilti:nightly
ssh-keygen -q -t ed25519 -N '' -f ./admin
cp ./admin.pub ./authorized_keys
cat >gilti.toml <<'EOF'
[instance]
root_title = "My Git server"
[access]
ssh = true
EOF
docker run --rm \
--read-only --cap-drop ALL \
--cap-add CHOWN --cap-add DAC_OVERRIDE --cap-add FOWNER \
--cap-add SETGID --cap-add SETUID --cap-add SYS_CHROOT \
--tmpfs /run:rw,nosuid,nodev,noexec,size=32m \
--tmpfs /tmp:rw,nosuid,nodev,noexec,size=256m \
-p 8080:8080 -p 2222:2222 \
-v gilti-state:/var/lib/gilti \
-v "$PWD/gilti.toml:/etc/gilti/config.toml:ro" \
-v "$PWD/authorized_keys:/etc/gilti/authorized_keys:ro" \
ghcr.io/dimidiumlabs/gilti:nightlyGilti requires the configuration path explicitly:
gilti --config /etc/gilti/config.toml
gilti --config /etc/gilti/config.toml --check
gilti --config /etc/gilti/config.toml shellThe filename extension selects JSON (.json), TOML (.toml), or YAML
(.yaml/.yml). Each file is a Serde representation of the typed Config
structure with server, instance, git_storage, git, lfs, browser,
access, and archive sections.
Unknown fields are rejected and omitted fields receive typed defaults. See the
complete deploy/gilti.toml example.
Durations use values such as 250ms, 10s, or 2m. Byte sizes accept explicit
units such as 32KiB and 10MiB. Git storage paths must be absolute and
normalized, and git_storage.repositories must be below git_storage.home.
When repositories is omitted, it defaults to the repositories directory
below the configured Git home. The container image and Helm chart mount their
persistent state at /var/lib/gilti; custom container storage paths must be
placed on a writable mount with suitable ownership.
server owns transport limits and trusted-proxy networks, git owns executable
paths and smart-HTTP policy, lfs owns object and request limits, and browser
owns pagination and presentation limits. archive.formats selects the download
formats; supported values are tar, tar.gz, tar.bz2, tar.xz, tar.zst,
and zip. TAR compression is streamed by native Rust codecs rather than
external compressor processes; its buffer and codec levels are configured in
the same archive section.
Gilti snapshots the authorized keys file at process startup; changing it takes effect after a restart. Repositories and the persistent SSH host key live on the state volume.
Configure the allowed public keys in values:
ssh:
authorizedKeys:
- ssh-ed25519 AAAA... operator@examplehelm upgrade --install gilti ./deploy/charts/gilti \
--namespace gilti --create-namespace \
--values values.yaml \
--set web.clonePrefix='ssh://git@git.dimidiumlabs.io/'See deploy/charts/gilti/README.md for persistence, routing,
and public-repository configuration.
Signed APK, DEB, and RPM packages for AMD64, ARM64, and RISC-V 64 are
published at pkg.dimidiumlabs.io/gilti. Use the
nightly channel for builds from main and the stable channel for v*
releases. GitHub releases also include standalone binaries and a SHA256SUMS
manifest. The packages install the service binary, default configuration, and
systemd or OpenRC service definition.
Native packages use the host OpenSSH daemon rather than starting another SSH
server. They create the dedicated git account and generate its
~/.ssh/authorized_keys with a restricted gilti shell forced command. Add one
plain public key per line to /etc/gilti/authorized_keys, then restart Gilti:
cat admin.pub | sudo tee -a /etc/gilti/authorized_keys
sudo systemctl restart gilti
ssh git@git.example.testOpenRC users can run sudo rc-service gilti restart. The package does not
replace or restart the host sshd; it relies on the normal
.ssh/authorized_keys lookup for the git user. It creates a password-locked
account and refuses to reuse an unmanaged existing git account. Do not enable
another authentication source or unrestricted authorized-keys file for this
account. If the host overrides AuthorizedKeysFile, include
/var/lib/gilti/git/.ssh/authorized_keys in that server configuration. To pass
Git wire-protocol v2 negotiation through OpenSSH, add the following scoped
setting and reload sshd; Git remains functional without it:
Match User git
AcceptEnv GIT_PROTOCOL
Provision tools and run static checks:
mise bootstrap
cargo fmt -- --check
cargo test --locked
cargo clippy --locked --all-targets -- -D warnings
shellcheck scripts/*.sh tests/*.sh
mise run chart -- --chart deploy/charts/gilti --lint-onlyFor layout work, run the source watcher after starting gilti-playground.service:
mise run devIt rebuilds the gilti binary and restarts the user service after changes to
Gilti sources or manifests. Set GILTI_DEV_SERVICE to use a different systemd
user unit. Browser refresh remains manual.
CI builds native AMD64, ARM64, and RISC-V 64 Linux artifacts once and uses them
for signed Linux packages and the multi-platform image; the Dockerfile does not
compile source code. Pushes to main update the nightly GitHub Release,
package repositories, image, and chart. A matching v* tag publishes the same
artifact version to the stable channels.
We welcome your contributions, including code, bug reports, ideas, and success stories.
If you are making a contribution for the first time or from a new email, please
add yourself to the .mailmap.
To include your code, we ask that you read and agree to the CLA. To
sign, add a CLA-Version: 1.0 and a Signed-off-by trailer to every commit
(git commit -s --trailer "CLA-Version: 1.0"). Each commit in a pull request
must carry a valid Signed-off-by line matching the commit author. Please use
your real name. We cannot include code from anonymous contributors.
AI agents MUST NOT add Signed-off-by tags. Only humans can legally certify the Contributor License Agreement.
You may use AI agents when writing code and documentation. AI is not allowed for media including images, videos, fonts at all. You must fully read, understand, and cleanup any code generated by the agent. We ask that you disclose the agent's use and indicate the tool, model, and extent of contribution.
Contributions should include an Assisted-by tag in the following format:
Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2], for example:
Assisted-by: Claude:claude-4.6-opus coccinelle sparse
Remember, AI agents should make software better, not worse.
Gilti source code is licensed under AGPL-3.0-or-later. Documentation is licensed under CC-BY-4.0. Components installed into the image retain their respective upstream licenses.