A portable Go CLI (kc) to manage Keycloak realms, users, groups and clients.
No Docker. No Java. A single static binary.
brew tap bcollard/keycloak-cli
brew install --cask keycloak-cligo install github.com/bcollard/keycloak-cli@latestGrab the latest release from GitHub Releases,
extract the archive and move kc to somewhere on your PATH.
git clone https://github.com/bcollard/keycloak-cli
cd keycloak-cli
make install # builds ./kc and copies to /usr/local/binkcbinary (see Install above)- A running Keycloak instance reachable over HTTPS
# one-time setup
kc config set server https://keycloak.example.com
kc config set username admin
kc config set password # prompts
kc config set secret-header-name my-header
kc config set secret-header-value # prompts
# authenticate (uses stored config — no flags needed)
kc login
# use
kc realm list
kc user list -r myrealm
kc client list -r myrealmAuthenticate with Keycloak. Stores the access + refresh token in ~/.config/kc/token.json.
Uses stored config as a fallback — if all values are already set via kc config, running
kc login requires no flags or prompts.
Flags:
-s, --server string Keycloak server URL (overrides KC_SERVER / stored config)
-u, --user string Admin username (overrides KC_ADMIN_USER / stored config)
--secret-header-name string Extra header name (overrides KC_ADMIN_SECRET_HEADER_NAME / stored config)
The header value is never accepted as a CLI flag — use KC_ADMIN_SECRET_HEADER_VALUE,
stored config, or kc login will prompt for it.
kc login
kc login -s https://kc.example.com -u admin
kc login --secret-header-name my-header
# logout
kc logoutkc config list
kc config show
kc config set server https://keycloak.example.com
kc config set username admin
kc config set password # prompts
kc config set secret-header-name my-header
kc config set secret-header-value # promptskc version
kc --versionkc realm list
kc realm create
kc realm delete # interactive multi-select + confirmation; master realm excludedNon-interactive (any flag triggers script mode):
kc realm create --name myrealm
kc realm delete --name myrealm --name otherrealmAll user commands support -r/--realm (flag → REALM_NAME env var → stored config → prompt).
kc user list # lists users enriched with their group memberships
kc user create # interactive: username, email, name, password, group assignment
kc user delete # interactive multi-select + confirmation
kc user set-password [username] # interactive picker + password prompt when no flags givenUser-identifier flags are interchangeable across
user/groupcommands:--usernameand--userare accepted everywhere a username is taken, and--temporary/--temporary-passwordare equivalent.
kc user list -r myrealm
kc user create -r myrealm
kc user delete -r myrealmNon-interactive create (any flag triggers script mode; --username is required):
kc user create -r myrealm \
--username alex --email alex@example.com --email-verified \
--password doe --group developersNon-interactive delete (any --username or --user-id triggers script mode; both repeatable):
kc user delete -r myrealm --username alex --username sam
kc user delete -r myrealm --user-id 7f8e...Set-password by positional username, --username/--user, or --user-id. The
password is prompted for if --password is omitted; with no selector at all an
interactive user picker is shown:
kc user set-password alex --password doe
kc user set-password --username alex --password doe --temporary
kc user set-password --user-id 7f8e... --password doe --temporarykc group list # lists groups enriched with their members
kc group create # interactive: name, optional parent group
kc group delete # interactive multi-select + confirmation
kc group add-member # multi-select users × groups (Cartesian assignment)kc group list -r myrealm
kc group add-member -r myrealmNon-interactive (any identifier flag triggers script mode; --name/--group matches group name or full path):
# create (optionally as subgroup of an existing group)
kc group create -r myrealm --name developers
kc group create -r myrealm --name backend --parent developers
# delete
kc group delete -r myrealm --name developers --name /tenants/acme
kc group delete -r myrealm --group-id 7f8e...
# add-member (Cartesian: every user × every group)
kc group add-member -r myrealm \
--user alex --user sam \
--group developers --group /tenants/acme
kc group add-member -r myrealm --user-id 7f8e... --group-id 1a2b...kc client list
kc client create # interactive: client ID, name, OAuth2 flows, redirect URIs
kc client delete # interactive multi-select + confirmationkc client list -r myrealm
kc client create -r myrealm
kc client delete -r myrealmkc client create flow picker supports:
- Authorization Code Flow
- Implicit Flow
- Resource Owner Password Credentials
- Client Credentials Grant
- Device Authorization Grant
- Token Exchange
Non-interactive mode (script-friendly):
kc client create -r myrealm \
--client-id client-a --secret secret-a \
--direct-access-grants --token-exchange
kc client create -r myrealm \
--client-id web-app --standard-flow \
--redirect-uri https://app.example.com/cb \
--attribute foo=barNon-interactive delete (any --client-id triggers script mode; repeatable):
kc client delete -r myrealm --client-id client-a --client-id web-appkc client-scope create # name + description; type=default, include.in.token.scope=true
kc client-scope add # attach a scope to a client (default or optional)
kc client-scope add-mapper # attach a protocol mapper to a scopeInteractive:
kc client-scope create -r myrealm
kc client-scope add -r myrealmNon-interactive (script-friendly):
# 1. create the scope
kc client-scope create -r myrealm \
--name add-client-b-as-audience \
--include-in-token-scope
# 2. add an audience protocol mapper to it
kc client-scope add-mapper -r myrealm \
--scope add-client-b-as-audience \
--name aud-client-b \
--mapper-type oidc-audience-mapper \
--config included.client.audience=client-b \
--config access.token.claim=true \
--config id.token.claim=false
# 3. attach the scope as optional to a client
kc client-scope add -r myrealm \
--client-id client-a \
--scope add-client-b-as-audience \
--type optional
--client-idand--clientare interchangeable onclient/client-scopecommands, as are--scopeand--namefor the scope name.
Use kc config to manage persistent configuration. Values here are the lowest-priority
fallback — overridden by env vars or CLI flags.
kc config list # list available keys
kc config show # show current values (sensitive keys masked)
kc config set <key> [value] # set a key; prompts for value if sensitive and omitted| Key | Description |
|---|---|
server |
Keycloak server URL |
username |
Admin username |
password |
Admin password (sensitive — masked in show) |
secret-header-name |
Extra header name forwarded on every admin request |
secret-header-value |
Extra header value (sensitive — masked in show) |
Config and token files are stored in ~/.config/kc/ (mode 0600).
The token is refreshed automatically before it expires.
Env vars override stored config. Useful for CI or scripting.
| Variable | Description |
|---|---|
KC_SERVER |
Keycloak server URL |
KC_ADMIN_USER |
Admin username |
KC_ADMIN_PASSWORD |
Admin password |
KC_ADMIN_SECRET_HEADER_NAME |
Extra header name |
KC_ADMIN_SECRET_HEADER_VALUE |
Extra header value |
REALM_NAME |
Pre-select realm for any command that asks for one |
Example .envrc (direnv):
export KC_SERVER="https://keycloak.example.com"
export KC_ADMIN_PASSWORD="<admin-password>"
# optional
export KC_ADMIN_SECRET_HEADER_NAME="my-header"
export KC_ADMIN_SECRET_HEADER_VALUE="<header-value>"
export REALM_NAME="my-realm"For every value: CLI flag → env var → stored config → interactive prompt
Releases are built with goreleaser and published to GitHub Releases
via the .github/workflows/release.yml workflow on every v* tag push.
Binaries are provided for:
- macOS (amd64, arm64)
- Linux (amd64, arm64)
The original shell scripts (scripts/) and Docker targets remain available for reference
but are no longer the primary interface. See make help for the full target list.