Farrier

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:

  1. 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.
  2. 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.
WhereEngineDataSecretsDNS-01 hook
Any VM or on-prem hostDocker Composelocal volume.env or files under /run/secretsBIND nsupdate, Infoblox
Kubernetes, OpenShiftStatefulSet, 1 replicaPVC (RWO)Kubernetes Secret, External Secretsany
AWSEKS or one EC2 instanceEBS gp3Secrets ManagerRoute 53
Google CloudGKE or Compute EnginePersistent DiskSecret ManagerCloud DNS
AzureAKS or a VMManaged DiskKey VaultAzure 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 -d

The 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/:

FileWhat it creates
farrier.yamlThe engine StatefulSet (1 replica, PVC, non-root, read-only root filesystem, all capabilities dropped), its ClusterIP Service and a ServiceAccount with no token
web.yamlThe dashboard Deployment (2 replicas) and Service
network-policy.yamlOnly the dashboard (and a monitoring namespace, for /metrics) may reach the engine
ingress.example.yamlAn ingress for the dashboard with TLS and a source-range allowlist
secrets.example.yamlThe 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.yaml

Set 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.

EKSGKEAKS
IdentityIRSA or EKS Pod IdentityWorkload Identity FederationMicrosoft Entra Workload ID
IngressAWS Load Balancer Controller (ALB)GCE Ingress + managed certificateApplication routing add-on
trustedProxyHops121
Storage class (SQLite only)gp3standard-rwomanaged-csi
DatabaseRDS PostgreSQLCloud SQL for PostgreSQLAzure Database for PostgreSQL
SecretsSecrets Manager via External SecretsSecret Manager via External SecretsKey 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. verify

Set 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 gp3 storage 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-challenge records 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-rwo or premium-rwo storage 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-sets with 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-csi storage 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 txt with 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, then docker 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.