Use this pattern when one Elixir/Phoenix application should run KMS in-process and store KMS key material locally.
The app calls the normal KMS API through KMS.Client.Local. KMS starts its OTP supervision tree with the application.
What runs
Embedded mode starts:
- the KMS OTP application;
- the configured KMS repo using SQLite by default;
KMS.Cache;- optional RMK-wrapped signing-key custody and caller-process signing;
- session cleanup jobs;
- optional fallback bootstrap;
- no HTTP API unless you also start the KMS web application.
The application and KMS share one BEAM VM and one trust boundary.
A narrow host application can start only selected KMS components. For example, an application that supplies its own Ecto repo and needs only process-local key caching can use:
config :kms, :components, [:cache]
config :kms, KMS.Repo, repo: MyApp.RepoWith no :sessions component, production startup does not require KMS session configuration. The host remains responsible for starting and migrating MyApp.Repo. Any KMS schemas used through that repo must have matching host migrations. This includes the signing_keys table when the host uses KMS.install_signing_key_once/1 and KMS.sign_digest/2.
When to use
Good fit:
- one application owns the encrypted data;
- app-level authorization is enough;
- you want KEK aliases, RMK wrapping, and KMS migrations;
- a separate KMS service would be operational overhead;
- SQLite persistence is sufficient for KMS key tables.
Examples:
- Phoenix app with encrypted Ecto fields;
- internal tool with a few encrypted secrets;
- single-node deployment with local disk backups;
- small SaaS app where one KEK alias per owner or tenant is enough.
Poor fit:
- multiple app servers need one central KMS authority;
- app hosts must not read KMS DB/RMK material;
- KMS needs separate deployment, audit, or access controls;
- you need a public/user-facing encryption API.
Use remote KMS for those.
Minimal Phoenix config
Start KMS with the Phoenix app:
def application do
[
mod: {MyApp.Application, []},
extra_applications: [:logger, :runtime_tools, :kms]
]
endConfigure local client, SQLite KMS repo, and local RMK:
config :my_app,
ecto_repos: [MyApp.Repo, KMS.Repo.SQLite]
config :kms, ecto_repos: [KMS.Repo.SQLite]
config :kms, KMS.Repo, repo: KMS.Repo.SQLite
config :kms, :client, KMS.Client.Local
config :kms, :rmk_provider, KMS.RMK.Local
config :kms, KMS.RMK.Local,
key_path: Path.expand("../priv/kms_rmk_#{config_env()}.key", __DIR__)In dev/test, point KMS SQLite at a writable file:
config :kms, KMS.Repo.SQLite,
database: Path.expand("../tmp/kms/kms_dev.sqlite3", __DIR__),
journal_mode: :wal,
foreign_keys: :on,
busy_timeout: 5_000,
priv: "priv/repo",
migration_primary_key: false,
migration_foreign_key: [type: :text],
migration_timestamps: [type: :utc_datetime_usec]Field-level encryption shape
Use an alias per owner/tenant:
alias_name = "owner:#{owner_id}"
{:ok, _kek} = KMS.create_alias(alias_name)Use row/field AAD:
aad = "document:#{document_id}/body"
{:ok, encrypted_body} = KMS.encrypt_alias(body, alias_name, aad: aad)
{:ok, body} = KMS.decrypt_alias(encrypted_body, alias_name, aad: aad)The Quickstart shows this inside a Phoenix changeset with a working example under example/phoenix_example.
Trust boundary
Embedded mode trusts the application process.
Local calls without session: are trusted/admin calls. That is fine when your application owns authorization and only calls KMS after normal app-level checks.
If KMS should enforce per-user authorization, pass session: and use principals/bindings. See Multi-user encryption.
Persistence and backup
With SQLite + local RMK, back up:
- KMS SQLite database and WAL/SHM sidecars;
- local RMK key file;
- application database rows containing ciphertext.
Keep RMK key backups access-controlled separately from database backups when possible.
Operational notes
- Use one writer per SQLite database file.
- Store SQLite and RMK files on durable disk, not ephemeral container filesystem.
- Disable production crash dumps:
ERL_CRASH_DUMP_BYTES=0. - Signing runs in calling processes. The sensitive KMS cache retains decoded signing material, and each active signing caller briefly receives that material.
- Use external RMK providers when DB backups need a separate key-service/IAM boundary.
- Do not enable
KMS.APIaccidentally; embedded mode does not need it.