Skip to content
Linux packages and containers

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.rpm

Packages 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 ghalet

Use --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 ghalet

Replace 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.yaml

For 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 ghalet

Removal 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.yaml

Set 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 -d

Increase 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.