- September 12, 2026
- 11 min read
Deploying FlightCTL on EKS: Edge Fleet Management with Keycloak OIDC
In this post walks through deploying flightctl on EKS with Keycloak fronting it for OIDC auth with proper RBAC groups, and a dedicated Network Load Balancer for the one piece of traffic an ALB genuinely can't handle — mutual TLS device enrollment.
All the Terraform, Helm values, resource manifests files, and Ansible playbooks referenced below live in the edge-fleet-management repo, including a bootc image build pipeline for the test devices themselves. This post covers the FlightCTL-on-EKS path end to end.
What is FlightCTL?
FlightCTL is an open source, CNCF-adjacent project (backed by Red Hat) for managing large fleets of edge devices and the workloads running on them through a declarative, Kubernetes-like API. Instead of SSH-ing into boxes one at a time, you describe the desired OS image and application state for a fleet, and FlightCTL rolls it out with staged, budgeted deployments and automatic rollback on failure.
Its core building blocks are: bootc/rpm-ostree for transactional, image-based OS updates; TPM-backed device enrollment and mutual TLS for identity; and Podman/MicroShift as the workload runtimes on the device side. The pieces that matter for this post are the API server, the Postgres-backed control plane, and the mTLS-secured agent endpoint devices enroll against — all of which run as a Helm-installed workload on the EKS cluster we're about to stand up.
A few useful links before diving in:
- GitHub repository — source, issues, and releases
- flightctl.io — project site and feature overview
- User documentation — install, provisioning, fleet management, and CLI reference
- Helm chart — the chart used for the install steps below
Provisioning the cluster
The cluster itself is unremarkable — a standard EKS cluster provisioned with Terraform. FlightCTL doesn't need anything exotic from the control plane; the interesting decisions all happen in how you expose it.
terraform init
terraform plan -out=tfplan
terraform apply tfplanOnce the cluster creation is complete we can run the AWS cli command below to update our local kubeconfig so we can use kubectl commands to authenticate with the EKS cluster
aws eks update-kubeconfig --region us-west-2 --name edge-fleet-mgmtKeycloak as the OIDC provider
FlightCTL supports several auth backends, but OIDC is the one that maps cleanly onto "different people should see different things" — admins, operators, and read-only viewers. Keycloak runs in-cluster, behind its own ALB ingress:
kubectl create namespace keycloak
kubectl apply -f keycloak.yaml -n keycloakCreate the ALB ingress for keycloak by running the command below
* Be sure to update the Certificate ARN to match your environment before running the command.*
kubectl apply -f deploy/keycloak/ingress.yamlCreate the flightctl realm
Navigate to the Keycloak URL in a browser and log in with the default admin credentials (admin:admin).
Select Manage realms from the left navigation bar, then click Create realm:

In the Create realm dialog, set Realm name to flightctl and click Create:

Create groups and test users
Create three groups whose names FlightCTL's RBAC depends on matching exactly:
flightctl-adminflightctl-operatorflightctl-viewer

Create three test users and assign each to one of the groups above, so RBAC can be verified end to end once FlightCTL is installed:

Create the UI client application
Go to Clients in the left navigation and click Create client to define the flightctl-ui application:

Under Capability config, turn off Client Authentication:

The Helm chart's values file has no field for a client secret, so a confidential client can't log in once FlightCTL is installed — the ConfigMap patch applied later on is what actually supplies that secret.
Open the Client scopes tab and select the client's dedicated scope to configure token mappers:

Add the standard claims using Add mapper → From predefined mappers:

Add a groups claim using Add mapper → By configuration:

Select Group Membership as the mapper type:

Turn off Full group path so only the group name — not the full hierarchical path — flows into the token's groups claim. This is exactly what FlightCTL's dynamic role assignment matches against:

With the realm, groups, users, and UI client in place, we're ready to install FlightCTL with Helm and point its OIDC config at this client.
Installing FlightCTL with Helm
FlightCTL ships an official Helm chart. Point the auth block at the Keycloak realm, set the expose method to none since we're building ingress ourselves, and set the database's fsGroup if you're using the built-in Postgres rather than something like RDS:
auth:
type: "oidc"
insecureSkipTlsVerify: true
oidc:
clientId: "<client id from keycloak>"
scopes: ["openid", "profile", "email", "roles", "offline_access"]
issuer: "<keycloak endpoint>/realms/flightctl"
externalOidcAuthority: "<keycloak endpoint>/realms/flightctl"
organizationAssignment:
type: "static"
organizationName: "default"
usernameClaim: ["upn"]
roleAssignment:
type: "dynamic"
claimPath: ["groups"]
exposeServicesMethod: "none"helm install edge-manager --namespace flightctl --create-namespace \
oci://quay.io/flightctl/charts/flightctl -f ./deploy/flightctl/values.yaml
kubectl get pods -n flightctlIngress, and the client-secret workaround
With the ALB ingress resources applied for the UI and API, there's one gap the Helm values file can't close: it has no field for the OIDC client secret. Without it, login fails even though everything else is configured correctly. The fix is a plain ConfigMap patch applied after install:
kubectl apply -f ./deploy/flightctl/ingress.yml
kubectl describe ing edgemanager-alb-ingress-api -n flightctl
kubectl describe ing edgemanager-alb-ingress-ui -n flightctlkubectl create configmap -n flightctl flightctl-api-config \
--from-file=./deploy/flightctl/config.yaml -o yaml --dry-run=client | kubectl apply -f -Setting up the CLI auth provider
The UI client's redirect URIs point at the ALB hostname, which doesn't work for a CLI login flow that redirects to localhost. Rather than juggling redirect URIs on one client, define a second Keycloak client for the CLI and register it with FlightCTL as its own auth provider:
Create this client the same way as flightctl-ui above — Clients → Create client — but give it its own Client ID (e.g. flightctl-cli), turn off Client Authentication, and set Valid redirect URIs to http://localhost:* so the CLI's local callback can complete:
Register this client as FlightCTL's CLI auth provider:
apiVersion: v1beta1
kind: AuthProvider
metadata:
name: flightctl-cli-auth-provider
spec:
providerType: oidc
displayName: "FlightCTL CLI"
issuer: "<keycloak endpoint>/realms/flightctl"
clientId: "<client id from keycloak>"
enabled: true
scopes: ["openid", "profile", "email", "roles", "offline_access"]
usernameClaim: ["upn"]
roleAssignment:
type: dynamic
claimPath: ["groups"]
separator: ':'The first login below still runs against the default provider — the flightctl-ui client — before the CLI provider exists, and that client's redirect URIs point at the ALB hostname, not localhost. Temporarily add http://localhost:* to flightctl-ui's redirect URIs so this bootstrap login can complete:

flightctl login <flightctl api server endpoint> --web
flightctl apply -f ./deploy/flightctl/cli-auth.yaml
flightctl login <flightctl api server endpoint> --web --provider flightctl-cli-auth-providerOnce the CLI provider works, revert flightctl-ui's redirect URIs back to just the ALB hostname.
Exposing the agent API for mTLS
This is the part that trips people up. Device agents authenticate to the agent-facing API on flightctl-api-agent:7443 using mutual TLS — each device presents a client certificate from FlightCTL's own enrollment CA, and flightctl-api terminates TLS and validates it directly. An ALB can't sit in front of that: it terminates TLS before the backend ever sees the connection, so its "mTLS passthrough" mode only forwards the client cert as HTTP headers rather than preserving the actual handshake. The agent API needs a Network Load Balancer doing raw TCP passthrough straight to the pod instead:
kubectl apply -f deploy/flightctl/agent-api-nlb-service.yml
kubectl get svc flightctl-api-agent-nlb -n flightctl
dig <agent api endpoint>Generate enrollment certificate
Generate an enrollment certificate that gets injected into each device so its FlightCTL agent can enroll:
flightctl certificate request --signer=enrollment --expiration=365d --output=embedded > config.yamlVerifying RBAC in the FlightCTL UI
With Keycloak and FlightCTL wired together, we can confirm the RBAC groups actually take effect by logging in as the two test users created earlier.
Login as Ram, since Ram is an admin he should have full permissions to add/edit/delete resources:

Login as Kelly, since Kelly has only viewer rights she should be able to view resources but not make any changes:

In a follow up post we will look at enrolling devices and setting up fleet.
Related Posts
RAM GOPINATHAN
September 17, 2026
Onboarding Edge Devices to FlightCTL with FIDO Device Onboarding
How I wired FDO's owner service, a bootc image layering go-fdo-client on the FlightCTL agent base, and two Ansible playbooks together so edge devices enroll into FlightCTL with zero manual steps.
RAM GOPINATHAN
September 14, 2026
Provisioning Devices and Onboarding to FlightCTL
Building a bootc image with the FlightCTL agent, baking it into an AMI, and provisioning EC2 test devices that inject their enrollment credentials via cloud-init and show up as pending enrollments in FlightCTL.
RAM GOPINATHAN
September 9, 2026
Building an all in one FDO server infrastructure on Image Mode RHEL
This post walks through building an all in one FDO server infrastructure on Image Mode RHEL for testing purposes