Skip to main content

Secrets

Runix can resolve secret values from external sources, keeping sensitive data out of your config files.

Secret Configuration​

Define secrets at the top level of runix.yaml:

secrets:
db_password:
type: env
value: DB_PASSWORD
api_key:
type: file
value: /run/secrets/api_key
vault_secret:
type: vault
value: secret/data/myapp#password

Secret Types​

Environment Variable (env)​

Reads the value from an environment variable on the host:

secrets:
db_password:
type: env
value: DB_PASSWORD

The environment variable must be set when Runix starts. If it's not set, the process fails to start with an error:

secret "db_password": environment variable "DB_PASSWORD" not set

File (file)​

Reads the value from a file on disk:

secrets:
tls_cert:
type: file
value: /etc/tls/server.crt

The file contents are read and trimmed of leading/trailing whitespace. If the file doesn't exist or isn't readable, the process fails to start.

Vault (vault)​

secrets:
db_password:
type: vault
value: secret/database#password

Runix reads Vault secrets using:

  • VAULT_ADDR: Vault base URL, for example http://127.0.0.1:8200
  • VAULT_TOKEN: token used for the request

The value format is path#key.

  • logical KV v2 path: secret/database#password
  • explicit KV v2 API path: secret/data/database#password
  • KV v1 path: secret/database#password

Runix tries KV v2 first, then falls back to KV v1. If the secret key is not present or Vault returns a non-200 response, process startup fails with an error.

Using Secrets in Processes​

Secrets are injected into process environment variables:

secrets:
db_password:
type: env
value: DB_PASSWORD

processes:
api:
entrypoint: ./cmd/api
env:
DATABASE_URL: "postgres://user:${db_password}@localhost:5432/mydb"

Secret Masking​

The secrets.MaskValue() function provides safe display of secret values:

InputMasked Output
short****
my-secret-keymy****ey
abc****

Values with 4 or fewer characters are fully masked. Longer values show the first 2 and last 2 characters.

Implementation​

The resolver is at internal/secrets/resolver.go:

func Resolve(cfg map[string]types.SecretRef) (map[string]string, error)
func MaskValue(val string) string
func IsSecretEnv(key string, secretKeys map[string]bool) bool

Security Considerations​

  • Secret values are never written to log files
  • The event store does not include secret values in payloads
  • Socket permissions (0o660) limit who can communicate with the daemon
  • File-based secrets should use restrictive permissions (e.g., 0o600)

What's Next​