Linux packages and containers
Release tags publish amd64 and arm64 binaries, .deb and .rpm packages,
and the multi-platform image ghcr.io/songmu/ghalet:TAG.
Archives, install.sh, and go install are also available.
APT/DNF repositories are not provided.
For Homebrew installation and macOS service integration, see the
getting started
.
Debian and RPM packages
Download the package for your architecture from GitHub Releases and check
it against SHA256SUMS. Build provenance also covers the packages:
gh attestation verify ./PACKAGE --repo Songmu/ghalet
sudo apt install ./PACKAGE.deb
# Or, on an RPM-based system:
sudo dnf install ./PACKAGE.rpmPackages include the official runner’s OS dependencies and procps.
GitHub CLI is not required: the supplied configuration uses an encrypted PAT
provided by systemd. Registration requires systemd 250 or later.
Install Git and the build tools your workflows need separately.
The packages are tested on Ubuntu 26.04 hosts and Rocky Linux 9 containers on both
architectures; check compatibility before deploying on other distributions.
Installation creates separate unprivileged accounts: ghalet for the controller
and ghalet-runner for runners and jobs. Their HOME directories are
/var/lib/ghalet and /var/lib/ghalet-runner.
The systemd unit runs as ghalet, and the supplied configuration sets
runner.user: ghalet-runner. A command-scoped sudo policy permits only the
runner-user helper as ghalet-runner, not root commands.
The controller belongs to the runner group to prepare execution files and
collect them after shutdown. Configuration is root-owned and readable by
ghalet; state, caches, and saved logs belong to ghalet.
Encrypted credential sources remain root-only; systemd delivers decrypted
credentials to the service account.
The runner can traverse /var/lib/ghalet to reach its own execution directories,
but cannot modify that parent directory. Installation does not create a live
configuration, enable the service, or start it.
The unit uses Type=notify: systemctl start waits until daemon initialization
is complete and the management socket can serve requests. This does not guarantee
a successful ghalet status --fail-on-error; target and execution diagnostics remain separate.
To create a GitHub App and configure its encrypted service key:
sudo ghalet systemd app setup --scope OWNER/REPO --scale-set project
sudo ghalet config validate --config /etc/ghalet/config.yaml
sudo systemctl enable --now ghaletUse --scope ORG for an organization. The wizard saves authentication to the
managed file without changing the static configuration. Add its printed target
YAML to the config; if the config is missing, save the complete suggestion
(including the package runner user and log path) before validation/start.
Follow the printed browser URLs; for SSH use the
port forwarding in App setup
.
To use a PAT instead, copy and edit the package example:
sudo install -o root -g ghalet -m 0640 \
/usr/share/ghalet/config.yaml.example /etc/ghalet/config.yaml
sudoedit /etc/ghalet/config.yaml
sudo ghalet systemd credential set default --prompt
sudo ghalet config validate --config /etc/ghalet/config.yaml
sudo systemctl enable --now ghaletReplace OWNER/REPO in the configuration and supply a PAT with the
appropriate runner administration permissions. Registration writes its
reference to the managed file’s defaults.credential, shared by targets without
other authentication. Static credentials remain authoritative; see
managed setup information
.
Package installation and upgrades never request secrets; App setup or credential registration
is a separate, explicit command. For App keys, multiple credentials, automation,
and rotation, see encrypted credentials
.
Do not put credentials inline in YAML or in shell command arguments.
On older systemd installations, configure an owner-only PAT file with
defaults.credential.tokenFile instead; see
operations
.
The unit uses StateDirectory=ghalet and CacheDirectory=ghalet,
which supply STATE_DIRECTORY and CACHE_DIRECTORY. Executions go under
/var/lib/ghalet/runs and runner archives and digests
under /var/cache/ghalet/runners. The sample places saved runner logs under
/var/log/ghalet/runners through logs.root. Daemon stdout and stderr
go to the journal; persistence, rotation, and retention follow the host’s
journald configuration.
sudo journalctl -u ghalet -f
sudo ghalet status --fail-on-error --config /etc/ghalet/config.yamlFor updates, stop the service normally and inspect any incomplete shutdown before replacing the package. Package upgrade scripts also stop the service, but never restart it automatically. Start it explicitly after the update; an existing enable-at-boot setting is preserved.
sudo systemctl stop ghalet
# Install the new package using apt or dnf.
sudo systemctl start ghaletRemoval stops and disables the service. Configuration, credentials, state,
logs, and both accounts remain, including after apt purge; remove them only
after inspecting retained executions. Package operations never delete GitHub
scale sets.
The unit uses KillMode=mixed and a 15-minute outer stop deadline.
Increase that deadline if changing the default 10-minute drain timeout.
See service deployment
for shutdown
and crash-cleanup semantics.
Docker Compose
The container runs ghalet and its Linux runner processes together, as UID/GID 10001. It contains runner prerequisites, Git, curl, and a shell, not a complete hosted-runner toolchain. Extend the image to install workflow-specific tools. It does not use systemd inside the container.
Copy examples/compose.yaml and examples/system-config.yaml into a new
deployment directory, naming the latter config.yaml. Set OWNER/REPO and
place the PAT in credentials/token. On a Linux Docker host, prepare the
bind-mounted credentials for the image’s user:
mkdir -p credentials
# Write the PAT to credentials/token using your secret-management tool.
sudo chown -R 10001:10001 credentials
sudo chmod 0700 credentials
sudo chmod 0600 credentials/token
export GHALET_VERSION=vX.Y.Z
docker compose run --rm ghalet config validate --config /etc/ghalet/config.yaml
docker compose up -d
docker compose exec ghalet ghalet status --fail-on-error --config /etc/ghalet/config.yamlSet GHALET_VERSION to a published release tag. Verify the image provenance
with gh attestation verify oci://ghcr.io/songmu/ghalet:TAG --repo Songmu/ghalet.
For other Docker hosts, check the effective owner and mode inside the
container; making a token world-readable is not a workaround.
Configuration and credentials are mounted read-only. Named volumes preserve
the HOME/state, cache, and logs, and are initialized with the image user’s
ownership. Run only one daemon against each set of volumes.
Do not use docker compose down --volumes when retaining state.
Management commands must select the same /etc/ghalet/config.yaml as the
daemon.
Compose enables a small init process to forward signals and reap orphaned processes, and allows 15 minutes for normal stop/drain. Stop normally before recreating the container or changing its image:
docker compose stop
docker compose pull
docker compose up -dIncrease stop_grace_period if increasing runner.drainTimeout.
Forced termination, container removal, and host failure can interrupt jobs.
Persisted records remain for diagnosis; replacing a container is not live
runner-process recovery.
Jobs run inside this Linux container, not on the host’s toolchain. macOS jobs still require a native macOS installation. Container packaging does not add Docker actions, job/service containers, or per-job isolation. The example mounts no Docker socket, shares no host PID namespace, and requires no privileged mode. Trusted jobs running as the same user can still read mounted management credentials and share HOME, tools, and caches.