After I fell in love with the way Coolify makes deployments stupidly simple, it was bound to stay. But I wanted to use OAuth to authenticate users.

Officially Coolify supports Azure, BitBucket, GitLab and Google OAuth providers. While not explicitly mentioned in the docs, it supports a few more (Authentik, Clerk, Discord, Infomaniak and Citadel).

But mine (PocketID) unfortunately isn’t on the list and all the providers differ in their implementations in minute details, like data structures or URL schemas.

Fake Clerk Proxy

An hour of back and forth with Claude later, Clerk was made out to be the closest match to PocketID. All that is needed is a small proxy that rewrites a few urls:

:80 {
    log {
        output file /var/log/caddy/access.log
        format json
    }

    @authorize path /oauth/authorize
    handle @authorize {
        uri query scope openid+email+profile
        redir {$POCKETID_URL}/authorize?{query} 302
    }

    rewrite /oauth/token    /api/oidc/token
    rewrite /oauth/userinfo /api/oidc/userinfo

    reverse_proxy {$POCKETID_URL}
}

Combined with a small Dockerfile:

FROM caddy:2-alpine
ENV POCKETID_URL=""
COPY Caddyfile /etc/caddy/Caddyfile
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -q --spider http://localhost/ || exit 1

Custom Claims

Coolify expects additional Claims from “Clerk” that PocketID does not provide by default, using a custom User Group that injects these hardcoded values we can fake it.

email_verified = true
user_id = pocketid-user

The actual values don’t matter, Coolify merely expects them to be set and then ignores it. Users are matched by their email address.

Configure Coolify OAuth

Once deployed and running, the “Clerk” OAuth can be configured as usual and the Base URL/Authentication Endpoint pointing to the “Clerk” Proxy.

PocketID being Passkeys-only is considered safe, but the old Username/Password login is vulnerable to brute-force attacks. To prevent this we add a redirect in the Server Configuration -> Proxy -> Dynamic Configurations we add a file called login-redirect.yaml (Don’t forget to replace {coolify-host} with your domain):

http:
  middlewares:
    redirect-to-clerk:
      redirectRegex:
        regex: "^https://{coolify-host}/login$"
        replacement: "https://{coolify-host}/auth/clerk/redirect"
        permanent: false

  routers:
    coolify-login-redirect:
      rule: "Host(`{coolify-host}`) && Path(`/login`)"
      middlewares:
        - redirect-to-clerk@file
      service: coolify
      entryPoints:
        - https
      tls:
        certresolver: letsencrypt

Coolify’s Traefik should automatically pick up dynamic configs and the next login should immediately redirect you to your OAuth Endpoint.

Further considerations

This will break authentication if the “Clerk” Proxy container or PocketID is ever not up and reachable.

Should either one of those go down we can place this shell script on the host as an easy way to gain emergency access:

#!/bin/sh

FILE="/data/coolify/proxy/dynamic/login-redirect.yaml"

if [ -f "$FILE" ]; then
    rm "$FILE"
    echo "Login redirect disabled — password login restored"
else
    cat > "$FILE" << 'EOF'
http:
  middlewares:
    redirect-to-clerk:
      redirectRegex:
        regex: "^https://{coolify-host}/login$"
        replacement: "https://{coolify-host}/auth/clerk/redirect"
        permanent: false

  routers:
    coolify-login-redirect:
      rule: "Host(`{coolify-host}`) && Path(`/login`)"
      middlewares:
        - redirect-to-clerk@file
      service: coolify
      entryPoints:
        - https
      tls:
        certresolver: letsencrypt
EOF
    echo "Login redirect enabled — passkey login enforced"
fi