The mental model

Authentik / Keycloak: full IdP. Users are stored in the IdP's database; the IdP issues OIDC tokens.

Dex: protocol bridge. Users live in an upstream system; Dex's job is "give me OIDC for these LDAP / GitHub / etc. users." Small, stateless (except for refresh tokens), no user management UI.

If you already have a user directory and just need OIDC in front of it, Dex is dramatically lighter than running a full IdP.

Install via Docker

docker run -d \
    --name dex \
    --restart unless-stopped \
    -p 127.0.0.1:5556:5556 \
    -v ./config.yaml:/etc/dex/config.yaml:ro \
    ghcr.io/dexidp/dex:latest dex serve /etc/dex/config.yaml

Configure with GitHub as the upstream

# config.yaml
issuer: https://dex.example.com

storage:
  type: sqlite3
  config:
    file: /var/dex/dex.db

web:
  http: 0.0.0.0:5556
  allowedOrigins: ["*"]

connectors:
  - type: github
    id: github
    name: GitHub
    config:
      clientID: <github-oauth-client-id>
      clientSecret: <github-oauth-secret>
      redirectURI: https://dex.example.com/callback
      orgs:
        - name: myorg
          teams: [ "platform", "engineering" ]
      loadAllGroups: true
      teamNameField: slug

  - type: ldap
    id: ldap
    name: Corporate LDAP
    config:
      host: ldap.example.com:636
      bindDN: cn=dex,ou=svc,dc=example,dc=com
      bindPW: <bind-password>
      usernamePrompt: Email
      userSearch:
        baseDN: ou=People,dc=example,dc=com
        filter: "(objectClass=person)"
        username: mail
        idAttr: uid
        emailAttr: mail
        nameAttr: cn
      groupSearch:
        baseDN: ou=Groups,dc=example,dc=com
        filter: "(objectClass=groupOfNames)"
        userMatchers:
          - userAttr: DN
            groupAttr: member
        nameAttr: cn

staticClients:
  - id: kubernetes
    name: Kubernetes cluster
    secret: <random-secret>
    redirectURIs:
      - http://localhost:8000             # for kubectl oidc-login
      - http://localhost:18000

  - id: argocd
    name: Argo CD
    secret: <random-secret>
    redirectURIs:
      - https://argocd.example.com/auth/callback
      - https://argocd.example.com/applications

  - id: grafana
    name: Grafana
    secret: <random-secret>
    redirectURIs:
      - https://grafana.example.com/login/generic_oauth

oauth2:
  skipApprovalScreen: true
  alwaysShowLoginScreen: false

The Kubernetes kubectl use case

For "OIDC-authenticated kubectl access for human users," Dex is the canonical bridge:

# Configure the K8s API server with OIDC
# Add to kube-apiserver flags:
#   --oidc-issuer-url=https://dex.example.com
#   --oidc-client-id=kubernetes
#   --oidc-username-claim=email
#   --oidc-groups-claim=groups

On the user's machine:

# Install kubelogin (the OIDC kubectl plugin)
go install github.com/int128/kubelogin/cmd/kubectl-oidc_login@latest

# kubeconfig entry
- name: dex-user
  user:
    exec:
      command: kubectl
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
        - oidc-login
        - get-token
        - --oidc-issuer-url=https://dex.example.com
        - --oidc-client-id=kubernetes
        - --oidc-client-secret=<same as in dex config>
        - --oidc-extra-scope=email,groups

# Use
kubectl --user=dex-user get pods
# Browser opens to dex → GitHub login → redirects back → kubectl gets token

RBAC bindings in K8s reference the user's email or GitHub team names; access is auto-managed by the source identity system.

For other apps

Any OIDC client can use Dex. Examples that already work:

  • Argo CD — built-in OIDC integration; point at Dex (see Argo CD tutorial)
  • Grafana — OIDC auth, group-mapped roles
  • Harbor — OIDC for container registry
  • cert-manager — OIDC for ACME challenges with external account binding
  • OpenShift — uses Dex as its OAuth flow component

Dex vs alternatives

  • Authentik (see that tutorial) — full IdP with own user store + UI + flows. Heavier; more features; better for "I want self-hosted SSO with its own user management."
  • Keycloak — Red Hat's heavyweight IdP; massive feature surface; Java-based; heavy operationally. Picks up where Dex stops if you need full IdP features.
  • Gluu, FusionAuth, ZITADEL — other full-featured IdPs; all heavier than Dex.
  • Plain reverse-proxy + oauth2-proxy / forward-auth — for "I just need to put SSO in front of one app," sometimes simpler than running an IdP at all.

When Dex is the right pick

  • You have an existing identity source (LDAP, GitHub org, SAML IdP) and just need OIDC tokens from it.
  • Kubernetes + kubectl OIDC access is the immediate driver.
  • You want a small + stateless OIDC bridge, not a full IdP with its own user database.

When it isn't

  • You don't have an upstream IdP — Dex doesn't store users itself. Authentik / Keycloak is the right shape.
  • You need MFA / passkey / fine-grained policy — Dex passes through upstream auth; multi-factor lives at the upstream.
  • You need a polished admin UI for user management — Dex has no such UI; everything is config files + the upstream.

Operational notes

  • Storage backends: sqlite3 (default), Postgres / MySQL (scale), etcd / Kubernetes CRDs (for K8s-native). The Kubernetes backend is interesting: client / connector / token state lives as CRs.
  • Refresh tokens are stored in the backend; rotation policies are configurable.
  • TLS: terminate at the reverse proxy (Caddy / Traefik / nginx); Dex itself supports TLS too but external proxy is the common pattern.

For "I run Kubernetes and want my engineers to authenticate to it via GitHub teams without standing up Keycloak," Dex is the right size in 2026.