Skip to main content

Keys from environment variables and files

Besides storing a secret in the database, a Key Store entry can read its value at task time from a file on the Semaphore server or from an environment variable of the Semaphore server process. This is useful when the credential is already provisioned outside Semaphore, for example:

  • an SSH key mounted into the Semaphore container as a Docker or Kubernetes secret;
  • a token written to disk by an agent (HashiCorp Vault Agent, cert-manager, etc.) and rotated regularly;
  • a password injected into the container environment by your orchestrator.

Semaphore does not copy the value into its database. Every time a task needs the key, the server reads the file or the variable again, so rotating the credential on disk takes effect on the next task.

info

The file or variable is read by the Semaphore server, not by a runner. When you use remote runners, mount the file on the server host; the server resolves the secret and hands it to the runner.

Choosing the source

When you create or edit a key (Key Store → New Key), the top of the form has source tabs:

TabWhere the value comes fromWhat to enter
LocalSemaphore database (encrypted)The login, password, or private key in the form
Storage ProExternal secret storage such as HashiCorp VaultStorage and the secret path
EnvAn environment variable of the Semaphore server processThe variable name, for example PROD_SSH_KEY
FileA file on the Semaphore serverThe absolute path to the file, for example /var/lib/semaphore/secrets/prod.json

With Env or File selected, the login, password, and private-key fields disappear. The whole credential, including the login for SSH and Login With Password keys, must be in the file or variable.

1. Allow the directory

For security, Semaphore only reads key files that are inside its secrets directory. Any other path is rejected when a task starts:

Failed to install inventory: file path must be inside secrets path

The default secrets directory is /tmp/semaphore. Point it at the directory where your key files live using dirs.secrets in config.json or the SEMAPHORE_SECRETS_PATH environment variable. See Secrets directory for precedence rules.

Docker Compose example that mounts a host directory and allows it:

services:
semaphore:
image: semaphoreui/semaphore:latest
environment:
SEMAPHORE_SECRETS_PATH: /var/lib/semaphore/secrets
volumes:
- /srv/semaphore/secrets:/var/lib/semaphore/secrets:ro

Equivalent config.json fragment:

{
"dirs": {
"secrets": "/var/lib/semaphore/secrets"
}
}

Rules for the path entered in the File tab:

  • it must be absolute (/var/lib/semaphore/secrets/prod.json, not prod.json);
  • it must not contain .. segments;
  • it must resolve to a location inside the secrets directory (subdirectories are fine);
  • the file must be readable by the user Semaphore runs as (in the official Docker image this is semaphore, UID 1001).

Environment variables have no such restriction; the server just reads the named variable from its own environment.

2. Format the value

The content of the file (or the value of the variable) depends on the key type. A single trailing newline at the end of a file is ignored; anything else is used verbatim.

SSH key

Semaphore expects a JSON document, not a raw PEM or OpenSSH private key file:

{
"login": "deploy",
"passphrase": "",
"private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n"
}
  • login — the SSH user name, passed to Ansible as --user. Leave it empty to let the inventory decide (ansible_user). For Git repositories an empty login defaults to git.
  • passphrase — passphrase of the private key, or an empty string.
  • private_key — the private key with line breaks encoded as \n.

Generate the wrapper from an existing key with jq, which takes care of the escaping:

jq -n --arg login deploy --rawfile key ~/.ssh/id_ed25519 \
'{login: $login, passphrase: "", private_key: $key}' \
> /srv/semaphore/secrets/prod_ssh.json
chmod 0400 /srv/semaphore/secrets/prod_ssh.json

Then create a key of type SSH, open the File tab, and enter /var/lib/semaphore/secrets/prod_ssh.json (the path as seen inside the container).

warning

Pointing the File tab at a raw private key such as ~/.ssh/id_ed25519 does not work. The file is parsed as JSON and the task fails to load the inventory.

Login With Password

Also a JSON document:

{
"login": "svc-ansible",
"password": "s3cr3t"
}

Leave login empty to use the key as a plain token or password, for example as an Ansible vault password.

Environment variable example

The same JSON format applies to the Env tab. In Docker Compose:

services:
semaphore:
image: semaphoreui/semaphore:latest
environment:
PROD_SSH_KEY: '{"login":"deploy","passphrase":"","private_key":"-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----\n"}'

Create an SSH key, select the Env tab, and enter PROD_SSH_KEY as the variable name.

tip

Environment variables are visible to every process in the container and often end up in orchestrator metadata and logs. Prefer the File tab with a mounted secret when you can.

Troubleshooting

ErrorCauseFix
file path must be absoluteA relative path was enteredEnter the full path starting with /
file path must not contain traversal segmentsThe path contains ..Enter the resolved path
file path must be inside secrets pathThe file is outside dirs.secretsSet SEMAPHORE_SECRETS_PATH to the directory of the file, or move the file
no such file or directoryThe path is wrong or not mounted into the containerCheck the volume mount and use the in-container path
permission deniedThe Semaphore process cannot read the fileFix file ownership or mode
invalid character '-' looking for beginning of valueA raw private key was given instead of the JSON wrapperWrap the key as shown above