Deployment
Docker
Quick Start
docker run -d \
--name featherci \
-p 8080:8080 \
-v featherci-data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
-e FEATHERCI_SECRET_KEY=$(openssl rand -base64 32) \
-e FEATHERCI_BASE_URL=https://ci.example.com \
-e FEATHERCI_ADMINS=yourusername \
-e FEATHERCI_GITHUB_CLIENT_ID=your_client_id \
-e FEATHERCI_GITHUB_CLIENT_SECRET=your_client_secret \
ghcr.io/featherci/featherci:latest
Docker Socket Mapping
FeatherCI executes build steps inside Docker containers. When running FeatherCI itself in Docker, mount the host's Docker socket for sibling containers:
-v /var/run/docker.sock:/var/run/docker.sock
Build containers run alongside FeatherCI on the host's Docker daemon, not nested inside it. This is simpler and more performant than Docker-in-Docker (dind).
Permissions
The FeatherCI process must have permission to access the Docker socket:
- Run as root (default in the official image)
- Match the docker group GID from the host:
docker run --group-add $(getent group docker | cut -d: -f3) ...
DOCKER_HOST
By default, FeatherCI connects via the unix socket at /var/run/docker.sock. For a remote Docker daemon:
-e DOCKER_HOST=tcp://docker-host:2375
Docker Compose
services:
featherci:
image: ghcr.io/featherci/featherci:latest
ports:
- "8080:8080"
volumes:
- featherci-data:/data
- /var/run/docker.sock:/var/run/docker.sock
environment:
FEATHERCI_SECRET_KEY: "${FEATHERCI_SECRET_KEY}"
FEATHERCI_BASE_URL: "https://ci.example.com"
FEATHERCI_ADMINS: "yourusername"
FEATHERCI_DATABASE_PATH: "/data/featherci.db"
FEATHERCI_CACHE_PATH: "/data/cache"
FEATHERCI_WORKSPACE_PATH: "/data/workspaces"
FEATHERCI_GITHUB_CLIENT_ID: "${FEATHERCI_GITHUB_CLIENT_ID}"
FEATHERCI_GITHUB_CLIENT_SECRET: "${FEATHERCI_GITHUB_CLIENT_SECRET}"
restart: unless-stopped
volumes:
featherci-data:
Create a .env file alongside your docker-compose.yml with the secret values:
FEATHERCI_SECRET_KEY=your-base64-key
FEATHERCI_GITHUB_CLIENT_ID=your_id
FEATHERCI_GITHUB_CLIENT_SECRET=your_secret
Data Persistence
Mount a volume at /data and configure paths:
| Variable | Recommended Value |
|---|---|
FEATHERCI_DATABASE_PATH | /data/featherci.db |
FEATHERCI_CACHE_PATH | /data/cache |
FEATHERCI_WORKSPACE_PATH | /data/workspaces |
Reverse Proxy
Place FeatherCI behind a reverse proxy for TLS termination. Set FEATHERCI_BASE_URL to the public HTTPS URL.
Caddy:
ci.example.com {
reverse_proxy featherci:8080
}
nginx:
server {
listen 443 ssl;
server_name ci.example.com;
ssl_certificate /etc/ssl/certs/ci.example.com.pem;
ssl_certificate_key /etc/ssl/private/ci.example.com.key;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Distributed Setup with Docker
# Master
docker run -d \
--name featherci-master \
-p 8080:8080 \
-v featherci-data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
-e FEATHERCI_MODE=master \
-e FEATHERCI_WORKER_SECRET=your-shared-secret \
-e FEATHERCI_SECRET_KEY=... \
-e FEATHERCI_BASE_URL=https://ci.example.com \
ghcr.io/featherci/featherci:latest
# Worker (on another host)
docker run -d \
--name featherci-worker \
-v /var/run/docker.sock:/var/run/docker.sock \
-e FEATHERCI_MODE=worker \
-e FEATHERCI_MASTER_URL=https://ci.example.com \
-e FEATHERCI_WORKER_SECRET=your-shared-secret \
ghcr.io/featherci/featherci:latest
Systemd
Run FeatherCI as a systemd service on Linux for automatic startup and process management.
Prerequisites
- FeatherCI binary installed (e.g. at
/usr/local/bin/featherci) - Docker installed and running
- A dedicated system user (optional but recommended)
Create a System User
sudo useradd --system --shell /usr/sbin/nologin --create-home --home-dir /var/lib/featherci featherci
sudo usermod -aG docker featherci
Configure
Create a YAML config file at /etc/featherci/config.yaml:
sudo mkdir -p /etc/featherci
sudo tee /etc/featherci/config.yaml > /dev/null <<'EOF'
bind_addr: ":8080"
base_url: "https://ci.example.com"
mode: standalone
database_path: /var/lib/featherci/featherci.db
secret_key: "your-base64-encoded-key"
admins:
- yourusername
max_concurrent: 2
cache_path: /var/lib/featherci/cache
workspace_path: /var/lib/featherci/workspaces
github:
client_id: "your_client_id"
client_secret: "your_client_secret"
EOF
sudo chmod 600 /etc/featherci/config.yaml
Generate the secret key with featherci --generate-key and paste it into the config file.
FeatherCI automatically checks /etc/featherci/config.yaml on startup. You can also specify a custom path with featherci --config /path/to/config.yaml or the FEATHERCI_CONFIG environment variable.
Alternative: Environment file
If you prefer environment variables, create /etc/featherci/featherci.env instead and reference it from the service file with EnvironmentFile=/etc/featherci/featherci.env:
FEATHERCI_SECRET_KEY=your-base64-encoded-key
FEATHERCI_BASE_URL=https://ci.example.com
FEATHERCI_ADMINS=yourusername
FEATHERCI_GITHUB_CLIENT_ID=your_client_id
FEATHERCI_GITHUB_CLIENT_SECRET=your_client_secret
FEATHERCI_DATABASE_PATH=/var/lib/featherci/featherci.db
FEATHERCI_CACHE_PATH=/var/lib/featherci/cache
FEATHERCI_WORKSPACE_PATH=/var/lib/featherci/workspaces
Environment variables always take precedence over the YAML config file.
Create the Service File
Create /etc/systemd/system/featherci.service:
[Unit]
Description=FeatherCI CI/CD Server
Documentation=https://featherci.github.io
After=network-online.target docker.service
Wants=network-online.target
Requires=docker.service
[Service]
Type=simple
User=featherci
Group=featherci
EnvironmentFile=/etc/featherci/featherci.env
ExecStart=/usr/local/bin/featherci
Restart=on-failure
RestartSec=5
WorkingDirectory=/var/lib/featherci
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/featherci
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Enable and Start
sudo systemctl daemon-reload
sudo systemctl enable featherci
sudo systemctl start featherci
Check Status
sudo systemctl status featherci
sudo journalctl -u featherci -f
Updating
# Download new binary
sudo curl -Lo /usr/local/bin/featherci https://github.com/featherci/featherci/releases/latest/download/featherci-linux-amd64
sudo chmod +x /usr/local/bin/featherci
# Restart
sudo systemctl restart featherci
Kubernetes
Experimental. Kubernetes deployment is functional but not yet battle-tested in production. Feedback is welcome.
FeatherCI can run in Kubernetes using the Docker socket from the host node. Build steps run as sibling containers on the same node — they are Docker containers, not Kubernetes pods.
Namespace
apiVersion: v1
kind: Namespace
metadata:
name: featherci
Secret
apiVersion: v1
kind: Secret
metadata:
name: featherci-config
namespace: featherci
type: Opaque
stringData:
FEATHERCI_SECRET_KEY: "your-base64-encoded-key"
FEATHERCI_GITHUB_CLIENT_ID: "your_client_id"
FEATHERCI_GITHUB_CLIENT_SECRET: "your_client_secret"
Persistent Volume Claim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: featherci-data
namespace: featherci
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: featherci
namespace: featherci
spec:
replicas: 1
selector:
matchLabels:
app: featherci
template:
metadata:
labels:
app: featherci
spec:
containers:
- name: featherci
image: ghcr.io/featherci/featherci:latest
ports:
- containerPort: 8080
env:
- name: FEATHERCI_BASE_URL
value: "https://ci.example.com"
- name: FEATHERCI_ADMINS
value: "yourusername"
- name: FEATHERCI_DATABASE_PATH
value: "/data/featherci.db"
- name: FEATHERCI_CACHE_PATH
value: "/data/cache"
- name: FEATHERCI_WORKSPACE_PATH
value: "/data/workspaces"
envFrom:
- secretRef:
name: featherci-config
volumeMounts:
- name: data
mountPath: /data
- name: docker-socket
mountPath: /var/run/docker.sock
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: featherci-data
- name: docker-socket
hostPath:
path: /var/run/docker.sock
type: Socket
Service
apiVersion: v1
kind: Service
metadata:
name: featherci
namespace: featherci
spec:
selector:
app: featherci
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: featherci
namespace: featherci
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- ci.example.com
secretName: featherci-tls
rules:
- host: ci.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: featherci
port:
number: 80
Important Notes
- Docker socket: The deployment mounts the host's Docker socket. Build containers run on the host's Docker daemon, not as Kubernetes pods.
- Single replica: FeatherCI uses SQLite, which does not support concurrent writes. Run a single replica in standalone mode.
- Node affinity: Ensure the pod always schedules on the same node (or use a ReadWriteOnce PVC that pins it).
- Security: Mounting the Docker socket gives the container root-equivalent access to the host.