Skip to main content
Version: Cloud (SaaS)

Self-hosted Agents

Self-hosted Agents lets you run jobs on your own hardware. This page explains what self-hosted agents are and how to install them on several platforms.

Overview​

Available On

An agent is a physical or virtual machine you own that can be dedicated to running Semaphore jobs. You can mix and match your own agents with the machines provided by Semaphore.

Self-hosted agents allow you to run workflows on machines that are not currently available as part of Semaphore Cloud plans, such as GPU-enabled machines for machine learning workloads.

Self hosted architecture

Self-hosted agents limitations​

Jobs running on self-hosted agents have the following limitations:

Agent lifecycle​

The agent attempts on startup to register with the Semaphore Control Plane by sending a registration request. Once registered, it waits for jobs. Repeated failure to register the agent causes it to shutdown.

The agent enters into a running state when a new job is available. Depending on its configuration, once the job is done the agent may disconnect and shutdown, or go back to the waiting state until a new job is available.

Agent communication​

Self-hosted agents use one-way communication to connect with Semaphore. Requests are always initiated by the agent and secured using HTTPS TLS 1.3. This means you don't need to inbound open ports in your firewall to use Semaphore in Hybrid mode.

Registration request​

When the agent boots up it sends a register request using a registration token. If the registration succeeds, the agent receives an access token to be used in all future communications and enters the waiting for job state.

note

A registration failure prevents the agent from connecting to the Semaphore Control Plane. Unregistered agents cannot run any jobs.

Sync request​

Waiting agents periodically send sync requests to the control plane with its state information. Semaphore responds with a continue message unless there is a job in the queue to be executed, in which case Semaphore sends the jobID.

Get job request​

When the agent receives a new jobID it enters the starting job state and sends a request to the /jobs endpoint. Semaphore responds with the job specs and an job log stream token.

Semaphore responds with a token used to stream the job output and the job specs, including commands, environment variables, files, prologues, epilogues, containers, among other details.

Job output request​

Agents running a job periodically send sync requests along with the output of the active job. Once the job is done, the agent sends the remainder of the logs and a sync request with the job result (passed or failed).

Disconnect request​

Depending on its configuration, the agent can either disconnect and shutdown after the job is finished, or go back to the waiting for job state.

Supported toolbox features​

Not all of the Semaphore toolbox commands are available on self-hosted agents. In some cases, you need additional setup steps to use these features.

FeatureAvailableNotes
Using the cacheOptionalUsing S3, GCP, or SFTP as a storage backend
Artifact storageYes
Test results and flaky testsYes
Checking code with checkoutYes
Starting debug jobsNoSee the self-hosted debug jobs
Changing language versions with sem-versionNo
Managing databases with sem-serviceNo

How to run jobs in self-hosted agents​

Once you have installed and configured the self-hosted agent, you can use it in your jobs by selecting the new agent type in your pipeline.

To run jobs on a self-hosted agent, follow these steps:

  1. Open your Semaphore project and press Edit Workflow
  2. Select the pipeline
  3. Under Environment Type select Self-hosted machine
  4. Select the machine from the selection list

Selecting a self-hosted agent

You can also change the agent for a single job using the agent override option.

Job sessions​

The self-hosted agent executes the job commands in two different ways depending on the platform where it is running:

  • On Linux and macOS, a new PTY session is created at the beginning of every job. All commands run in that single session
  • On Windows, PYT sessions are not used. Instead, each command is executed in a new PowerShell process with powershell -NonInteractive -NoProfile

See self-hosted configuration to learn how to run jobs in isolation.

Initialization agents​

If you want to run initialization jobs on self-hosted agents, you must change the default initialization agent. You can do this in two places:

  • Organization: affects all projects in the organization. See organization init agent to learn how to change this setting
  • Project: changes the agent running initialization for a single project. See project pre-flight checks to learn how to change this setting

Machines running initialization jobs must provide additional software. See initialization job requirements.

Initialization job requirements​

Initialization jobs are supported on Linux agents only, on the x86_64 and arm64 architectures. Self-hosted macOS and Windows agents cannot run initialization jobs.

The initialization job clones the repository with Git, compiles the pipeline with spc, and uploads the compiled pipeline and the initialization log as artifacts.

Where the requirements apply​

Initialization jobs run directly on the agent machine, so the software listed below must be installed there. The initialization job specifies no container of its own, so it is never run in a Docker environment — even on an agent whose regular jobs are.

Kubernetes agents are the exception: they run every job in a pod, so an initialization job uses the image set by the agent's kubernetes-default-image option, and fails with no containers specified in Semaphore YAML, and no default container is provided when that option is not set. That image must contain the Semaphore toolbox as well as a supported Erlang — the agent does not install the toolbox into pods — and the install-ordering rule in Erlang and the PATH applies when building it.

Required software​

SoftwareNotes
GitAny reasonably recent version. Git 2.25 or newer additionally enables an optimized blobless, sparse checkout, which is gated on an organization feature and is otherwise a standard shallow clone
Erlang/OTPMust be major version 24, 25, 26, or 27. Both erl and escript must be resolvable — see Erlang and the PATH
BashJob commands run in a login shell
OpenSSH clientClones the repository and fetches additional commits when evaluating change_in
CA certificatesUploads the compiled pipeline and the initialization log
A standard userlandCoreutils plus sed, grep, tar, and curl, as shipped by any normal distribution image
whichUsed by spc to locate the condition evaluator. Provided by debianutils on Debian and Ubuntu, and easy to miss on a minimal image
sudoUsed while installing the agent and toolbox. Not needed at job time
digOnly when SEMAPHORE_GIT_CLONE_SLOW_RETRY is enabled, which adds a GitHub-specific alternative-endpoint fallback to checkout. Provided by dnsutils on Debian and Ubuntu

Docker, Elixir, and Git LFS are not required to run initialization jobs. Install them only if your regular jobs need them.

Erlang and the PATH​

Installing a supported Erlang is not sufficient on its own. Both erl and escript must be resolvable in two different contexts, and they are not the same shell:

  • At agent install time, in a non-login shell. The agent installer runs the toolbox installer as sudo -u <agent-user> -H bash …, which sources no profile file and uses sudo's secure_path. A version manager that activates Erlang from a profile file is not visible here. Install Erlang system-wide, in a root-owned directory such as /usr/bin or /usr/local/bin
  • At job time, in a login shell. Here a version manager does work, as long as it activates Erlang from ~/.bash_profile

Check both contexts, as the user the agent runs as:

Check Erlang in both contexts
$ sudo -u <agent-user> -H bash -c 'command -v erl'
/usr/bin/erl
$ sudo -u <agent-user> -H bash -lc 'command -v escript'
/usr/bin/escript

A non-login check such as ssh <machine> 'command -v escript' reports a false negative for anything a version manager activates, which is why the second form uses bash -lc.

warning

Activation must be in ~/.bash_profile specifically. Bash reads only the first of ~/.bash_profile, ~/.bash_login, and ~/.profile for login shells, and installing the agent creates ~/.bash_profile. On a distribution that ships only ~/.profile and ~/.bashrc — Ubuntu, for example — ~/.profile stops being read from that point on, and so does ~/.bashrc, which Ubuntu's ~/.profile is what loads.

Activation placed in ~/.bashrc or ~/.profile, which is what asdf and mise instruct by default, therefore works before the agent is installed and stops working afterwards.

warning

Install Erlang before you install the agent. The toolbox selects a condition evaluator matching the Erlang version present at installation time, and the installer does not fail the installation when no matching binary exists — you get an agent that reports a successful install but has no condition evaluator.

If you install or change Erlang afterwards, re-extract the toolbox tarball and then run ~/.toolbox/install-toolbox, or re-run the agent installer. Re-running ~/.toolbox/install-toolbox on its own is not enough: it moves the matching when_otp_<major> binary out of ~/.toolbox, so a second run can find nothing to install and will not report the failure. Expect an error line for spc, which is moved the same way — that one is harmless.

Install and verify​

On Ubuntu 22.04 and 24.04:

Initialization job requirements on Ubuntu
apt-get update && apt-get install -y --no-install-recommends \
git openssh-client ca-certificates curl tar sudo debianutils erlang-nox
update-ca-certificates

Run the checks below as the user the agent runs as, in a login shell.

Check the Erlang version
$ sudo -u <agent-user> -H bash -lc "erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell"
"27"

If the reported version falls outside 24 to 27, install a supported release from the Erlang downloads page.

Check the initialization job toolchain
$ sudo -u <agent-user> -H bash -lc 'missing=0
for b in git erl escript which checkout when spc artifact retry; do
if p=$(command -v "$b"); then printf "%-10s %s\n" "$b" "$p"
else printf "%-10s MISSING\n" "$b"; missing=1; fi
done; exit $missing'
git /usr/bin/git
erl /usr/bin/erl
escript /usr/bin/escript
which /usr/bin/which
checkout checkout
when /usr/local/bin/when
spc /usr/local/bin/spc
artifact /usr/local/bin/artifact
retry /usr/local/bin/retry

The command exits non-zero when anything is missing. Every line must show a path, except checkout, which is a shell function and correctly shows as a bare name.

MISSING next to when means the Erlang version is unsupported, or Erlang was installed after the agent. MISSING next to checkout means ~/.bash_profile no longer sources ~/.toolbox/toolbox. Both are covered in Erlang and the PATH.

Finally, confirm the condition evaluator runs:

Check the condition evaluator
$ sudo -u <agent-user> -H bash -lc 'echo "[]" > /tmp/in.json && when list-inputs --input /tmp/in.json --output /tmp/out.json && cat /tmp/out.json'
[]
info

If pipelines fail during initialization, see the initialization job logs to troubleshoot the issue.

How to debug jobs on self-hosted​

Before you can debug jobs you must enable self-hosted debugging on to the project settings.

Debug jobs work in a different way on self-hosted agents. Instead of connecting directly to the job via SSH as in cloud debug jobs, Semaphore starts the debug job and displays the name of the agent that is running the job. You must connect to the host running the agent and debug the job manually.

Keep in mind that:

  • You should log in with the same user the agent is running under. For example, if you're using agent-aws-stack, the user is semaphore
  • The agent does not automatically load environment variables for the job. To load the variables, you must source the files located at /tmp/.env-*

See also​