keycloak-migrator
v0.1.10
Published
CLI tool to author and run Keycloak migrations from any Node.js project.
Readme
keycloak-migrator
Keycloak instances differ across environments, and manually recreating the same realms, clients, and restricted data (roles, users, etc.) is brittle. keycloak-migrator provides a repeatable, code-first workflow to define those changes once and apply them everywhere—local dev boxes, staging, production—using the Keycloak Admin API. Treat your Keycloak configuration like any other migration: check it into Git, run it in CI, and keep every environment in sync without pointing and clicking.
Installation
yarn add --dev keycloak-migrator
# or
npm install --save-dev keycloak-migratorExpose the CLI through your package scripts (this makes yarn keycloak-migrator <command> work):
{
"scripts": {
"keycloak-migrator": "keycloak-migrator"
}
}Quick start
- Copy
keycloak-migrator.config.jsto your project root and edit it (TypeScript and JSON are supported too, but JS makes it easy to referenceprocess.env). - Run
yarn keycloak-migrator create addClientsto scaffold a migration. - Open the generated file in
migrationDir, add your Keycloak Admin Client calls, and export theKeycloakMigration. - Execute
yarn keycloak-migrator migrate(or--seed) to apply the files. Applied IDs are stored on the realm so runs are idempotent.
Configuration reference
keycloak-migrator.config.js (example):
/** @type {import("keycloak-migrator").KeycloakMigratorConfig} */
const config = {
migrationDir: "./keycloak/migrations",
seedDir: "./keycloak/seeds",
tsconfigPath: "./tsconfig.json",
keycloak: {
baseUrl: process.env.KEYCLOAK_BASE_URL ?? "http://localhost:8080",
realm: process.env.KEYCLOAK_REALM ?? "example-realm",
adminUsername: process.env.KEYCLOAK_ADMIN_USER ?? "admin",
adminPassword: process.env.KEYCLOAK_ADMIN_PASSWORD ?? "admin",
},
bootstrap: {
ensureClient: true,
client: {
clientId: "example-api",
name: "Example API",
publicClient: false,
redirectUris: ["*"],
webOrigins: ["+"],
directAccessGrantsEnabled: true,
serviceAccountsEnabled: true,
rootUrl: "https://example.com",
baseUrl: "/callback",
},
},
};
module.exports = config;Prefer TypeScript? Use keycloak-migrator.config.ts with the exported type:
import type { KeycloakMigratorConfig } from "keycloak-migrator";
const config: KeycloakMigratorConfig = {
migrationDir: "./keycloak/migrations",
// ...
};
export default config;migrationDir— Where migration files live. Point it atsrc/...when authoring TypeScript files ordist/...for compiled JavaScript. Relative paths resolve from the config file location.seedDir— Optional directory for seed files. Defaults to<migrationDir>/seeds.tsconfigPath— Optional path to thetsconfig.jsonthat defines your path aliases. If omitted we look for atsconfig.jsonnext to the config file.keycloak.baseUrl— Keycloak base URL (e.g.http://localhost:8080).keycloak.realm— Target realm. The CLI automatically creates it if it does not exist.keycloak.adminUsername/keycloak.adminPassword— Admin credentials used for theadmin-clilogin.bootstrap.ensureClient— Set totrueto ensure the client supplied inbootstrap.clientexists (created automatically if missing).bootstrap.client— Optional client payload passed straight tokc.clients.create. Because we use the officialClientRepresentationtype, any Keycloak client property (redirect URIs, flows, secrets, etc.) is supported.
You may keep several config files (JS, TS, or JSON) and pass a different one with --config path/to/config.js.
CLI commands
yarn keycloak-migrator create <name> [--seed] [--config path]
yarn keycloak-migrator migrate [--seed] [--config path]creategeneratestimestamp_name.tsinsidemigrationDir(orseedDirwhen--seed). The file importsKeycloakMigrationfrom this package and includes a stubbedrunfunction.migrateexecutes every.ts/.jsfile in the target directory in lexical order. TypeScript files are compiled at runtime viats-node, so you can run them without building if you prefer.
Writing migrations
import { KeycloakMigration } from "keycloak-migrator";
const migration: KeycloakMigration = {
id: "202402091530_addRolesToClient",
description: "Seed initial roles",
run: async (kc, realm) => {
const client = await kc.clients.find({ realm, clientId: "example-api" });
// implement your Keycloak Admin Client logic here
},
};
export default migration;kcis an authenticatedKeycloakAdminClient.realmis the realm from the config file.- Throwing aborts the run and stops subsequent migrations.
Bootstrapping behavior
- Realm bootstrap: the CLI always verifies the target realm exists before running migrations. Missing realms are created automatically.
- Client bootstrap: enable
bootstrap.ensureClientto ensure a client with the sameclientIdasbootstrap.clientexists. All provided fields (redirect URIs, flows, service account options, etc.) are forwarded tokc.clients.create, with onlyrealm,clientId, and sensible defaults (enabled,alwaysDisplayInConsole) added automatically. This keeps the bootstrap layer flexible for future Keycloak versions.
Tips
- Track migrations in Git. CI can run
yarn keycloak-migrator migrateto guarantee target Keycloak realms are up to date. - For production deployments, consider pointing
migrationDirto built JavaScript output and runningyarn keycloak-migrator migrate --config keycloak-migrator.prod.json. - If you see TypeScript errors about Node globals, install
@types/node(or keep the lightweight shims included in this repo).
