DE

Dex

OpenID Connect identity provider with pluggable connectors

Identity & SSO ★ 11.1k stars Medium setup Apache-2.0

Dex is an identity service that uses OpenID Connect to drive authentication for other applications. It acts as a portal to other identity providers through connectors such as LDAP, SAML and GitHub.

Key features

  • OIDC federation broker
  • Connectors for LDAP, SAML and GitHub
  • Popular in Kubernetes environments
  • Lightweight and stateless

Pros & cons

Strengths

  • Great Kubernetes integration
  • Simple and reliable

Trade-offs

  • No user management of its own

Dex replaces

Last reviewed Aug 26, 2026 · 851 words

Dex will not create a single user for you, and that fact settles whether it belongs in your stack. It is a translator: your apps speak OpenID Connect to Dex, and Dex speaks LDAP, SAML, GitHub, Google, Microsoft, or another OIDC provider to wherever your users actually live. If you already have a user store, Dex is the smallest, calmest way to put single sign-on in front of 20 apps. If you do not, you will be bringing one, and at that point a full identity provider is usually less work.

Where it sits in a stack

Dex grew up in Kubernetes. It is how kubectl gets OIDC logins against a GitHub organisation, and Argo CD ships with a copy built in. The homelab shape looks like this: lldap holds the users and groups in a container with a real admin UI, Dex brokers those to OIDC, and every app that speaks OIDC (Grafana, Gitea, Outline, Immich, Nextcloud) gets 1 login. Apps that speak nothing at all go behind oauth2-proxy, which itself authenticates against Dex. The result is a Go binary under 128 MB doing the federation and 0 per-app user tables.

A config that works, in about 40 lines

Dex is a single binary driven by 1 YAML file. The ghcr.io/dexidp/dex image runs dex serve /etc/dex/config.yaml; mount something like this:

issuer: https://dex.example.com
storage:
  type: sqlite3
  config:
    file: /var/dex/dex.db
web:
  http: 0.0.0.0:5556
connectors:
  - type: ldap
    id: ldap
    name: Home directory
    config:
      host: lldap:3890
      insecureNoSSL: true
      bindDN: uid=admin,ou=people,dc=example,dc=com
      bindPW: change-me
      userSearch:
        baseDN: ou=people,dc=example,dc=com
        filter: "(objectClass=person)"
        username: uid
        idAttr: uid
        emailAttr: mail
        nameAttr: displayName
      groupSearch:
        baseDN: ou=groups,dc=example,dc=com
        filter: "(objectClass=groupOfUniqueNames)"
        userMatchers:
          - userAttr: dn
            groupAttr: uniqueMember
        nameAttr: cn
  - type: github
    id: github
    name: GitHub
    config:
      clientID: $GITHUB_CLIENT_ID
      clientSecret: $GITHUB_CLIENT_SECRET
      redirectURI: https://dex.example.com/callback
      orgs:
        - name: my-org
staticClients:
  - id: grafana
    name: Grafana
    secret: a-long-random-string
    redirectURIs:
      - https://grafana.example.com/login/generic_oauth

Three things bite. issuer must match the public URL exactly, because every client validates it and the discovery document at /.well-known/openid-configuration is derived from it. insecureNoSSL is fine inside a Docker network and wrong for anything that crosses a host boundary. And each app is a staticClients entry with a hand-written secret, which is the entire client-management story: there is no UI to add one, so keep the file in git.

Nearly stateless, which also means no admin panel

The only state Dex keeps is in-flight auth requests, refresh tokens, and signing keys, which is why SQLite in a single file is enough and why it restarts in about 1 second. The flip side is everything it lacks: no user list, no password reset, no MFA of its own, no login-attempt dashboard. If a user needs a second factor, that is the upstream provider's job, and with plain LDAP that means there is none. Dex does support enablePasswordDB: true with bcrypt-hashed staticPasswords for testing. Do not let that become your production user store.

Operationally, that thinness is the reward. Backing up Dex is copying 1 SQLite file, or nothing at all if you point storage at Postgres or at Kubernetes custom resources. Rotating a client secret is editing the YAML and restarting, which takes about 1 second. Upgrades are pulling a new image; the project has been stable for years and its release notes are short. I have run instances for months without opening a log, which I cannot say about any of the full identity providers below.

Dex against the full identity providers

DexAutheliaAuthentikKeycloak
Has its own usersNoFile or LDAPYesYes
Admin UINoneNoneFullFull
MFA built inNoTOTP, WebAuthnTOTP, WebAuthn, moreYes
Idle RAMunder 128 MBabout 50 MBabout 1 GBabout 1 GB
Best fitKubernetes, existing directoryLogin wall for a homelabOne IdP for everythingEnterprise, SAML-heavy

Authentik is what most people reading this actually want: users, groups, MFA, a UI, and OIDC plus SAML plus LDAP out of 1 deployment. Authelia is the lighter option when the main need is a login wall in front of a reverse proxy. Dex wins when the directory already exists, when you are running Kubernetes, or when you value a component whose whole config fits on 1 screen.

What I'd do

Running Kubernetes, or already holding users in LDAP, Active Directory, or a GitHub organisation: Dex with the SQLite store, behind a proxy with TLS, and lldap if you still need a directory that is also small. Building homelab SSO from nothing for a handful of Docker apps: skip Dex, go straight to Authentik, and accept the gigabyte. The worst outcome is Dex plus a hand-maintained static password list, which is the least secure and most tedious of every option on that table.

Compare Dex

28 head-to-head comparisons.

Similar identity & sso apps