Reference
Environment Variables
Complete reference of all FeatherCI configuration variables. Values can be set as environment variables or in a .env file. Environment variables take precedence.
Server
| Variable | Default | Description |
|---|---|---|
FEATHERCI_BIND_ADDR | :8080 | Address and port the HTTP server binds to |
FEATHERCI_BASE_URL | http://localhost:8080 | Public URL for OAuth callbacks and webhook URLs. Required in production. |
FEATHERCI_MODE | standalone | Operating mode: standalone, master, or worker |
Security
| Variable | Default | Description |
|---|---|---|
FEATHERCI_SECRET_KEY | — | Required. 32-byte base64-encoded key for AES-256-GCM encryption. Generate with featherci --generate-key. |
FEATHERCI_ADMINS | — | Required. Comma-separated list of usernames with admin privileges. |
Database
| Variable | Default | Description |
|---|---|---|
FEATHERCI_DATABASE_PATH | ./featherci.db | Path to the SQLite database file |
Storage
| Variable | Default | Description |
|---|---|---|
FEATHERCI_CACHE_PATH | ./cache | Directory for build cache storage |
FEATHERCI_WORKSPACE_PATH | ./workspaces | Directory for build workspaces (cloned repositories) |
GitHub OAuth
| Variable | Default | Description |
|---|---|---|
FEATHERCI_GITHUB_CLIENT_ID | — | GitHub OAuth application client ID |
FEATHERCI_GITHUB_CLIENT_SECRET | — | GitHub OAuth application client secret |
Both must be set to enable GitHub authentication. Callback URL: {BASE_URL}/auth/github/callback
GitLab OAuth
| Variable | Default | Description |
|---|---|---|
FEATHERCI_GITLAB_URL | https://gitlab.com | GitLab instance base URL (for self-hosted) |
FEATHERCI_GITLAB_CLIENT_ID | — | GitLab OAuth application ID |
FEATHERCI_GITLAB_CLIENT_SECRET | — | GitLab OAuth application secret |
Both client ID and secret must be set to enable GitLab authentication. Callback URL: {BASE_URL}/auth/gitlab/callback
Gitea / Forgejo OAuth
| Variable | Default | Description |
|---|---|---|
FEATHERCI_GITEA_URL | — | Required when Gitea auth is enabled. Base URL of your Gitea/Forgejo instance. |
FEATHERCI_GITEA_CLIENT_ID | — | Gitea/Forgejo OAuth application client ID |
FEATHERCI_GITEA_CLIENT_SECRET | — | Gitea/Forgejo OAuth application client secret |
All three must be set to enable Gitea authentication. Callback URL: {BASE_URL}/auth/gitea/callback
Distributed Mode
| Variable | Default | Description |
|---|---|---|
FEATHERCI_WORKER_SECRET | — | Required for master and worker modes. Shared secret for worker authentication. |
FEATHERCI_MASTER_URL | — | Required for worker mode. URL of the master instance. |
FEATHERCI_MAX_CONCURRENT | 2 | Maximum number of build steps a worker runs in parallel. |
Validation Rules
FEATHERCI_SECRET_KEYmust decode to exactly 32 bytesFEATHERCI_MODEmust be one of:standalone,master,worker- At least one OAuth provider must be fully configured (unless running with
--dev) - At least one admin username must be specified (unless running with
--dev) FEATHERCI_MASTER_URLis required when mode isworkerFEATHERCI_WORKER_SECRETis required when mode ismasterorworkerFEATHERCI_GITEA_URLis required when Gitea OAuth credentials are provided
Workflow YAML Schema
Complete schema for .featherci/workflow.yml files.
Top-Level Fields
name: string # Display name of the workflow (required)
on: TriggerConfig # When the workflow should trigger (required)
steps: []Step # Steps to execute (required)
Trigger Configuration
on:
push:
branches: [string] # Branch glob patterns (optional, default: all)
tags: [string] # Tag glob patterns (optional)
pull_request:
branches: [string] # Target branch patterns (optional, default: all)
An empty trigger enables it for all events of that type:
on:
push: # Triggers on all pushes
pull_request: # Triggers on all PRs
Step Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | — | Unique step identifier |
type | string | No | "" (command) | Step type: "" for command, "approval" for manual gate |
image | string | Yes* | — | Docker image. *Required for command steps, ignored for approval. |
commands | []string | No | — | Shell commands to execute in the container |
depends_on | []string | No | — | Step names that must complete first |
env | map | No | — | Environment variables. Use $SECRET_NAME to reference secrets. |
working_dir | string | No | Repo root | Working directory for commands |
timeout_minutes | int | No | 60 | Maximum execution time in minutes |
if | string | No | — | Condition expression for step execution |
continue_on_error | bool | No | false | Continue workflow even if this step fails |
cache | object | No | — | Build cache configuration |
secrets | []string | No | — | Secret names to inject as env vars |
services | []object | No | — | Sidecar containers (databases, caches) to run alongside the step |
Cache Configuration
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Cache key template. Supports {{ checksum "file" }} and {{ .Branch }}. |
paths | []string | Yes | Directories to cache between builds |
Service Containers
Service containers run alongside your step on a shared Docker network. Each service is accessible by hostname (derived from the image name).
| Field | Type | Required | Description |
|---|---|---|---|
image | string | Yes | Docker image for the service (e.g., mysql:8.0) |
env | map | No | Environment variables for the service container |
Hostname resolution: The image name (without tag or registry prefix) becomes the hostname. For example:
mysql:8.0→ hostnamemysqlredis:7-alpine→ hostnamerediscircleci/postgres:14→ hostnamepostgres
Example:
- name: integration-test
image: ruby:3.4
services:
- image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: test
MYSQL_DATABASE: app_test
- image: redis:7
env:
DATABASE_URL: mysql2://root:test@mysql/app_test
REDIS_URL: redis://redis:6379
commands:
- bundle exec rspec spec/integration
Condition Expressions
The if field accepts expressions in the format: variable operator "value"
Variables: branch
| Operator | Description |
|---|---|
== | Exact match |
!= | Not equal |
=~ | Glob pattern match |
!~ | Glob pattern not match |
Examples:
if: branch == "main"
if: branch != "develop"
if: branch =~ "release/*"
if: branch !~ "feature/*"
Complete Example
name: Production Pipeline
on:
push:
branches: [main, develop]
tags: [v*]
pull_request:
branches: [main]
steps:
- name: test
image: golang:1.22
timeout_minutes: 30
cache:
key: go-{{ checksum "go.sum" }}
paths:
- /go/pkg/mod
commands:
- go test -v -race ./...
- name: lint
image: golang:1.22
continue_on_error: true
commands:
- golangci-lint run
- name: build
image: golang:1.22
depends_on: [test, lint]
commands:
- CGO_ENABLED=0 go build -o app ./cmd/app
- name: approve-deploy
type: approval
depends_on: [build]
- name: deploy
image: alpine:latest
depends_on: [approve-deploy]
if: branch == "main"
secrets: [DEPLOY_KEY]
env:
DEPLOY_KEY: $DEPLOY_KEY
commands:
- ./deploy.sh production
API Endpoints
FeatherCI exposes a small set of public HTTP endpoints. The web UI is server-rendered HTML — there is no general-purpose REST API at this time.
Health Check
GET /health
Returns 200 OK with a JSON body when the server is running:
{"status": "ok"}
No authentication required. Use this for load balancer health checks.
Readiness Check
GET /ready
Returns 200 OK when the server is ready to accept traffic (database is reachable):
{"status": "ready"}
Returns 503 Service Unavailable if the database is not reachable:
{"status": "not ready", "error": "database unavailable"}
No authentication required. Use this for Kubernetes readiness probes.
Webhook Endpoints
These endpoints receive events from Git providers. They are registered automatically when you add a project.
POST /webhooks/github
POST /webhooks/gitlab
POST /webhooks/gitea
Each endpoint validates the request signature using the provider-specific mechanism (GitHub HMAC, GitLab token, Gitea HMAC). Unsigned or incorrectly signed requests are rejected.
Worker API
The worker API is used internally for distributed mode communication. All endpoints require a Bearer token matching FEATHERCI_WORKER_SECRET.
GET /api/worker/steps/ready # Poll for ready steps
POST /api/worker/steps/{id}/claim # Claim a step (409 if taken)
POST /api/worker/steps/{id}/complete # Report step completion
POST /api/worker/steps/{id}/log # Upload step log file
GET /api/worker/builds/{id} # Get build metadata
GET /api/worker/builds/{id}/steps # List build steps
POST /api/worker/builds/{id}/started # Mark build started
GET /api/worker/projects/{id} # Get project metadata
GET /api/worker/projects/{id}/secrets # Get decrypted secrets
GET /api/worker/projects/{id}/token # Get clone token
POST /api/worker/register # Register worker
POST /api/worker/heartbeat # Worker heartbeat
POST /api/worker/status # Update worker status
POST /api/worker/offline # Set worker offline
Note: The worker API is internal. It may change between versions.