Deploy anywhere
Farrier is two containers: the engine (ghcr.io/gagankarthik/farrier, the API and the renewal loop) and the web app (ghcr.io/gagankarthik/farrier-web, the dashboard). Both images are multi-arch (amd64 and arm64), run as a non-root user, and are signed with cosign on every release.
Two facts shape every deployment:
- The engine must reach your devices. It talks to F5, FortiGate and PAN-OS management interfaces, SSH hosts and TLS endpoints. Run it in the network segment, VPC or cluster that can route to them. The dashboard only needs to reach the engine.
- The engine keeps state in SQLite. Run exactly one engine replica, on block storage (EBS, Persistent Disk, Azure Managed Disk, a local or SAN volume). Do not put the data directory on NFS, SMB, EFS or Azure Files: SQLite locking on network filesystems is unreliable. The web app is stateless and scales freely.
| Where | Engine | Data | Secrets | DNS-01 hook |
|---|---|---|---|---|
| Any VM or on-prem host | Docker Compose | local volume | .env or files under /run/secrets | BIND nsupdate, Infoblox |
| Kubernetes, OpenShift | StatefulSet, 1 replica | PVC (RWO) | Kubernetes Secret, External Secrets | any |
| AWS | EKS or one EC2 instance | EBS gp3 | Secrets Manager | Route 53 |
| Google Cloud | GKE or Compute Engine | Persistent Disk | Secret Manager | Cloud DNS |
| Azure | AKS or a VM | Managed Disk | Key Vault | Azure DNS |
Docker Compose (any VM, on-prem)
The quickest production setup, on one Linux host:
git clone https://github.com/gagankarthik/farrier && cd farrier
cat > .env <<EOF
FARRIER_API_TOKEN=$(openssl rand -hex 32)
FARRIER_DASHBOARD_PASSWORD=$(openssl rand -hex 16)
EOF
docker compose up -dThe dashboard listens on port 3000. Put a TLS-terminating reverse proxy (Caddy, nginx, Traefik or your load balancer) in front, and keep port 8740 unpublished: only the web container talks to the engine. The proxy must append to (or overwrite) X-Forwarded-For; set FARRIER_TRUSTED_PROXY_HOPS to the number of proxies that do (default 1, see Behind proxies). With single sign-on, set FARRIER_OIDC_REDIRECT_URI to https://<host>/auth/callback: production requires it. Back up the farrier-data volume.
Kubernetes (EKS, GKE, AKS, OpenShift, any cluster)
Manifests live in deploy/kubernetes/:
| File | What it creates |
|---|---|
farrier.yaml | The engine StatefulSet (1 replica, PVC, non-root, read-only root filesystem, all capabilities dropped), its ClusterIP Service and a ServiceAccount with no token |
web.yaml | The dashboard Deployment (2 replicas) and Service |
network-policy.yaml | Only the dashboard (and a monitoring namespace, for /metrics) may reach the engine |
ingress.example.yaml | An ingress for the dashboard with TLS and a source-range allowlist |
secrets.example.yaml | The secrets to create, with the commands that generate them |
kubectl create namespace farrier
kubectl apply -f my-secrets.yaml # from secrets.example.yaml, or External Secrets
kubectl apply -k deploy/kubernetes
kubectl apply -f my-ingress.yaml # from ingress.example.yamlSet the storage class on the volume claim to your cluster's block storage. OpenShift assigns its own UID range: remove runAsUser and fsGroup from farrier.yaml and the restricted-v2 SCC will run it as is.
Managed Kubernetes (EKS, GKE, AKS)
The Helm chart ships a complete values file per cloud (deploy/helm/farrier/examples/values-<cloud>.yaml), and deploy/terraform/{eks,gke,aks} create the cloud side next to an existing cluster: the workload identity, KMS key, secrets, bucket and, optionally, the database. Use the managed database: the engine then runs two or more replicas with one elected leader.
| EKS | GKE | AKS | |
|---|---|---|---|
| Identity | IRSA or EKS Pod Identity | Workload Identity Federation | Microsoft Entra Workload ID |
| Ingress | AWS Load Balancer Controller (ALB) | GCE Ingress + managed certificate | Application routing add-on |
trustedProxyHops | 1 | 2 | 1 |
| Storage class (SQLite only) | gp3 | standard-rwo | managed-csi |
| Database | RDS PostgreSQL | Cloud SQL for PostgreSQL | Azure Database for PostgreSQL |
| Secrets | Secrets Manager via External Secrets | Secret Manager via External Secrets | Key Vault via External Secrets |
terraform -chdir=deploy/terraform/eks apply # 1. cloud side
terraform -chdir=deploy/terraform/eks output -raw kubernetes_manifests | kubectl apply -f -
terraform -chdir=deploy/terraform/eks output -raw helm_values > cloud-values.yaml
helm install farrier deploy/helm/farrier -n farrier \
-f deploy/helm/farrier/examples/values-eks.yaml -f cloud-values.yaml # 2. install
curl -fsS https://<dashboard host>/api/health # 3. verifySet cloud.provider (the examples do): the engine's network policy keeps node metadata endpoints blocked and opens only what that cloud's workload identity needs. Every identity is keyless: no access keys, key files or client secrets.
AWS
- Compute. EKS with the manifests above, or one EC2 instance with Docker Compose. Place the engine in subnets that route to your devices' management VLANs, through Direct Connect or a site-to-site VPN for on-prem appliances.
- Storage. An EBS gp3 volume (the
gp3storage class on EKS). Snapshot it with AWS Backup. - Secrets. Store the API token, dashboard password, master key and device credentials in Secrets Manager, and sync them into the cluster with the External Secrets Operator using IRSA. Mount device credentials under
/run/secrets/farrier. - DNS-01. A hook script that calls
aws route53 change-resource-record-sets, with an IAM role allowed to change only the_acme-challengerecords of your zones. - Access. An internal ALB with Cognito or OIDC authentication in front of the dashboard.
Google Cloud
- Compute. GKE (Standard or Autopilot) with the manifests above, or a Compute Engine VM with Docker Compose. Reach on-prem devices over Cloud VPN or Interconnect.
- Storage. A Persistent Disk through the
standard-rwoorpremium-rwostorage class, with snapshot schedules. - Secrets. Secret Manager with the Secret Manager CSI add-on or External Secrets, using Workload Identity.
- DNS-01. A hook that calls
gcloud dns record-setswith a service account limited to DNS record changes on your zone. - Access. An internal HTTPS load balancer with Identity-Aware Proxy in front of the dashboard.
Azure
- Compute. AKS with the manifests above, or a Linux VM with Docker Compose. Reach on-prem devices over VPN Gateway or ExpressRoute.
- Storage. An Azure Managed Disk through the
managed-csistorage class. Do not use Azure Files for the engine. - Secrets. Key Vault through the Secrets Store CSI driver with workload identity.
- DNS-01. A hook that calls
az network dns record-set txtwith a managed identity scoped to your DNS zone. - Access. Application Gateway with Entra ID authentication, or an internal ingress behind your VPN.
On-prem and air-gapped
- Run Docker Compose on a VM in the management network, or use the Kubernetes manifests on your own cluster or OpenShift.
- For internal names, use the built-in private CA or your own ACME server (step-ca, an internal EJBCA or AD CS with an ACME front end), so nothing leaves the network.
- Mirror the two images into your registry (
docker pull,docker save,docker load, thendocker push) and pin them by digest. - Back up the data directory and keep the master key in a separate vault: a backup without its key cannot be decrypted, which is the point.
After deploying
Work through the hosting checklist: tokens set, dashboard behind a login, secrets allowlisted, least-privilege device accounts, backups tested, and /metrics scraped with an expiry alert.