This guide gets GARM running in Docker with the LXD provider. By the end, you will have a working GARM instance that can create self-hosted GitHub Actions runners on demand.
- A Linux host with Docker installed
- LXD installed and initialized (
sudo lxd init --autoif you haven't already) - A GitHub PAT, GitHub App, or Gitea token with the required permissions
sudo mkdir -p /etc/garmCreate /etc/garm/config.toml:
sudo tee /etc/garm/config.toml > /dev/null <<'EOF'
[default]
enable_webhook_management = true
[logging]
enable_log_streamer = true
log_format = "text"
log_level = "info"
log_source = false
[metrics]
enable = true
disable_auth = false
[jwt_auth]
# CHANGE THIS to a random string (32+ characters).
secret = ")9gk_4A6KrXz9D2u`0@MPea*sd6W`%@5MAWpWWJ3P3EqW~qB!!(Vd$FhNc*eU4vG"
time_to_live = "8760h"
[apiserver]
bind = "0.0.0.0"
port = 80
use_tls = false
[apiserver.webui]
enable = true
[database]
backend = "sqlite3"
# CHANGE THIS to a random 32-character string.
passphrase = "shreotsinWadquidAitNefayctowUrph"
[database.sqlite3]
db_file = "/etc/garm/garm.db"
[[provider]]
name = "lxd_local"
provider_type = "external"
description = "Local LXD installation"
[provider.external]
provider_executable = "/opt/garm/providers.d/garm-provider-lxd"
config_file = "/etc/garm/garm-provider-lxd.toml"
EOFCreate /etc/garm/garm-provider-lxd.toml:
sudo tee /etc/garm/garm-provider-lxd.toml > /dev/null <<'EOF'
unix_socket_path = "/var/snap/lxd/common/lxd/unix.socket"
include_default_profile = false
instance_type = "container"
secure_boot = false
project_name = "default"
url = ""
client_certificate = ""
client_key = ""
tls_server_certificate = ""
[image_remotes]
[image_remotes.ubuntu]
addr = "https://cloud-images.ubuntu.com/releases"
public = true
protocol = "simplestreams"
skip_verify = false
[image_remotes.ubuntu_daily]
addr = "https://cloud-images.ubuntu.com/daily"
public = true
protocol = "simplestreams"
skip_verify = false
[image_remotes.images]
addr = "https://images.lxd.canonical.com"
public = true
protocol = "simplestreams"
skip_verify = false
EOFLXD and Docker can conflict on iptables rules. Without this fix, LXD containers (your runners) will not have internet access:
sudo iptables -I DOCKER-USER -j ACCEPTImportant
This rule does not persist across reboots. To make it permanent, use iptables-persistent or add it to a startup script.
Replace the image tag with the latest release version:
docker run -d \
--name garm \
-p 80:80 \
-v /etc/garm:/etc/garm:rw \
-v /var/snap/lxd/common/lxd/unix.socket:/var/snap/lxd/common/lxd/unix.socket:rw \
ghcr.io/cloudbase/garm:v0.2.0-beta1Check the logs:
docker logs garmYou should see lines like:
level=INFO msg="Loading provider" provider=lxd_local
level=INFO msg="setting up metric routes"
level=INFO msg="register metrics"
Copy the CLI from the container:
sudo docker cp garm:/bin/garm-cli /usr/local/bin/garm-cli
sudo chmod +x /usr/local/bin/garm-cliOr download the release binary:
wget -q -O - \
https://github.com/cloudbase/garm/releases/latest/download/garm-cli-linux-amd64.tgz \
| sudo tar xzf - -C /usr/local/bin/Replace garm.example.com with the hostname or IP where GARM is reachable:
garm-cli init --name="my_garm" --url http://garm.example.comYou will be prompted for a username, email, and password. These are your admin credentials.
The output shows your admin user and controller details:
Admin user information:
+----------+--------------------------------------+
| FIELD | VALUE |
+----------+--------------------------------------+
| ID | 4f38839b-a10e-4732-9bba-4abb235583a9 |
| Username | admin |
| Email | admin@example.com |
| Enabled | true |
+----------+--------------------------------------+
Controller information:
+---------------------------+-----------------------------------------------------------------------------+
| FIELD | VALUE |
+---------------------------+-----------------------------------------------------------------------------+
| Controller ID | 9febbf3f-a8ab-4952-9b5b-0416444492b5 |
| Metadata URL | http://garm.example.com/api/v1/metadata |
| Callback URL | http://garm.example.com/api/v1/callbacks |
| Webhook Base URL | http://garm.example.com/webhooks |
| Controller Webhook URL | http://garm.example.com/webhooks/9febbf3f-a8ab-4952-9b5b-0416444492b5 |
| Agent URL | http://garm.example.com/agent |
| GARM agent tools sync URL | https://api.github.com/repos/cloudbase/garm-agent/releases |
| Tools sync enabled | false |
| Minimum Job Age Backoff | 30 |
| Version | v0.2.0-beta1 |
+---------------------------+-----------------------------------------------------------------------------+
Key URLs to verify:
- Metadata URL and Callback URL must be reachable by the runner instances.
- Webhook Base URL / Controller Webhook URL must be reachable by GitHub/Gitea.
By default, GARM derives all URLs from the --url you passed to init. If your setup has different internal and external addresses (e.g. behind a reverse proxy or NAT), you can override individual URLs at init time:
garm-cli init --name="my_garm" --url http://garm.example.com \
--callback-url https://internal.example.com/api/v1/callbacks \
--metadata-url https://internal.example.com/api/v1/metadata \
--webhook-url https://external.example.com/webhooks \
--ca-bundle /path/to/ca-bundle.pem # optional: for internal CAsYou can also change these later with garm-cli controller update. See Controller settings for details.
Each garm-cli init creates a CLI profile stored locally. To manage multiple GARM instances, add profiles and switch between them:
garm-cli profile add --name="prod_garm" --url https://garm-prod.example.com
garm-cli profile switch prod_garmYour GARM instance is running. Continue with First Steps to add credentials, a repository, and your first runner pool.
For a more maintainable setup, create a docker-compose.yaml:
services:
garm:
image: ghcr.io/cloudbase/garm:v0.2.0-beta1
container_name: garm
restart: always
volumes:
- /etc/garm:/etc/garm:rw
- /var/snap/lxd/common/lxd/unix.socket:/var/snap/lxd/common/lxd/unix.socket:rw
ports:
- "80:80"Start with:
docker compose up -dThen apply the iptables fix and continue from step 6 (Install garm-cli) above.
Tip: The GARM container image includes provider binaries for LXD, Incus, Azure, AWS, GCP, OpenStack, OCI, and Kubernetes in
/opt/garm/providers.d/. You can use any of them by adding the corresponding[[provider]]section to your config.