Skip to content
Development documentation
This describes Keydra as it is being built and is not a released version. What it documents can change before a release.

Deploying Keydra

What Keydra needs, which of the three published images to run, and what to configure before it is reachable by anybody other than you.

Requirements#

What Keydra needs to run
Component Requirement

Java

21, for a source build. The published image carries its own runtime.

PostgreSQL

Required. Keydra keeps its own data here and there is no alternative — the application is non-blocking end to end and uses a reactive PostgreSQL driver.

Container engine

Podman or Docker, to run the image. The repository is built with Podman and its manifests are Kubernetes Pod manifests rather than Compose files.

A target

At least one, reachable from wherever Keydra runs. Redis, Valkey or a compatible fork, an Aerospike or a TiKV.

Redis for Keydra’s own store

Optional. Needed only when more than one instance runs against one database. Never one of your targets.

ClickHouse

Optional. Only if readings should outlive a restart.

An SMTP relay

Optional. Only if invitations and password resets should arrive by mail.

Note:Node and the build toolchain

Node 24.19.0 and yarn are needed only to build the frontend from source. A deployment that runs the published image needs neither.

The published images#

Keydra is published at quay.io/keydrahq as three images. Which one you want depends on whether the interface is served by Keydra or by something else.

Image What it is

quay.io/keydrahq/keydra

The interface built into the API’s own static resources, so one container serves both. This is the ordinary deployment and the one the manifests in the repository use.

quay.io/keydrahq/keydra-backend

The API alone. For a deployment that serves the interface separately — from a CDN, from an existing web server, or as its own pod.

quay.io/keydrahq/keydra-ui

The interface, and the proxy it needs. The image carries nginx: it serves the static files and routes /api/v1 and /graphql to whatever KEYDRA_BACKEND names, WebSocket upgrades included. So a split deployment does not need a reverse proxy in front of both — this is that proxy.

Important:The interface and the API must share an origin

The interface calls /api/v1 and /graphql as relative paths. There is no setting that points it at an API somewhere else, deliberately: an absolute API address in a static bundle is a build-time decision about a run-time fact, and it is also a cross-origin cookie problem.

So the interface and the API have to answer on one address. quay.io/keydrahq/keydra-ui is built to be that address: it serves the interface at / and forwards /api/v1 and /graphql — and the WebSocket upgrades on them — to KEYDRA_BACKEND. The session cookie stays a first-party cookie, which is what makes SameSite=Lax work.

Put your own proxy in front of it if you already have one, but do not put one between the two: the routing is already done.

If you have no reason to run them apart, use quay.io/keydrahq/keydra.

Note:Tags

The examples here use latest. A deployment should pin a version instead, so a restart does not become an upgrade nobody scheduled.

Deploying Keydra#

Prerequisites
  • Podman, or another engine that can run an OCI image.

  • A PostgreSQL Keydra can reach, or the one in the manifest below.

Procedure
  1. Create the secrets. KEYDRA_SECRET_KEY encrypts every stored credential:

    bash
    printf 'keydra' | podman secret create keydra-db-password -
    openssl rand -base64 32 | podman secret create keydra-secret-key -
    Important:Important

    Losing KEYDRA_SECRET_KEY means losing every stored credential. Sharing it means sharing them. Keep it wherever your other secrets live, not in the repository.

  2. Pull the image:

    bash
    podman pull quay.io/keydrahq/keydra:latest
  3. Run it, together with a PostgreSQL:

    bash
    podman play kube deploy/keydra-prod.yaml

    The manifest names a locally built image. Point it at the published one:

    yaml
        - name: keydra
          image: quay.io/keydrahq/keydra:latest
Verification
bash
curl -s http://localhost:8181/q/health/ready

Installing on Kubernetes with Helm#

The chart installs either shape: one image serving the API and the interface it calls, or the two apart. It will not install what it cannot start, so two of the steps below are refusals you would otherwise meet as a CrashLoopBackOff.

Prerequisites
  • A Kubernetes cluster and Helm 3.

  • A PostgreSQL the cluster can reach. The chart does not install one — what is kept there is every connection profile, account, grant and audit row, and a database packaged into an application’s chart is a database nobody is backing up, running on a pod whose replacement is what an upgrade is.

Procedure
  1. Make the Secret. The chart will not generate the key, and that is deliberate: a generated key is regenerated on the next upgrade, and the instance comes back unable to read a single stored credential — reported one target at a time rather than as a failure to start.

    bash
    kubectl create secret generic keydra \
      --from-literal=secret-key="$(openssl rand -base64 32)" \
      --from-literal=database-password='...'
    Important:Important

    Losing that key means losing every stored credential. Sharing it means sharing them.

  2. Add the chart repository:

    bash
    helm repo add keydra https://keydrahq.github.io/keydra-helm
    helm repo update

    The same package is also published as an OCI artifact, for a cluster that pulls everything from one registry: helm install keydra oci://quay.io/keydrahq/charts/keydra --version <version>.

  3. Install:

    bash
    helm install keydra keydra/keydra \
      --set database.url=postgresql://postgres:5432/keydra \
      --set existingSecret.name=keydra
  4. Tell it that it is behind a proxy, which under an Ingress it always is:

    bash
    helm upgrade keydra keydra/keydra --reuse-values \
      --set proxy.enabled=true \
      --set proxy.trusted=10.0.0.0/8 \
      --set publicUrl=https://keydra.example.com \
      --set ingress.enabled=true \
      --set ingress.hosts[0].host=keydra.example.com

    Told nothing, Keydra sees every sign-in as coming from the ingress controller: the checks that compare a sign-in with the ones before it then compare everybody with everybody, and the limit on attempts counts the whole cluster as one network. Naming the proxies is not optional — with the switch on and nobody named, any client can claim any address, and the chart refuses to render.

Verification
  • helm test keydra starts a pod that asks the release what only a running installation can answer: that it is ready, that the interface is served, and that /q/metrics is 404 on the address people are given.

  • There are no accounts yet. Create the first administrator once, through an endpoint that stops working the moment there is one:

    bash
    curl -fsS -X POST https://keydra.example.com/api/v1/auth/setup \
      -H 'Content-Type: application/json' \
      -d '{"username":"you","password":"..."}'
The two shapes

mode: standalone is the default and is one image. mode: split deploys quay.io/keydrahq/keydra-backend and quay.io/keydrahq/keydra-ui as two Deployments behind two Services, which is worth the second one when the two scale differently — several API replicas behind one set of static files — or when the interface belongs somewhere the API does not.

bash
helm install keydra keydra/keydra --set mode=split ...

You do not name the images. The chart picks them from the mode, because a default that is right for one shape is wrong for the other and quietly pulling the all-in-one into a split deployment would look like it worked.

Installing on Kubernetes with the operator#

The operator installs the same objects the chart does, from a resource the cluster owns instead of from a release Helm keeps. For installing Keydra the two do the same thing; the operator is the answer when you want the cluster to keep it that way afterwards, or when you want a target to be a resource rather than something typed into a form.

Prerequisites
  • A Kubernetes cluster, or an OpenShift.

  • A PostgreSQL the cluster can reach. The operator does not install one, for the reason the chart does not: what is kept there is every connection profile, account, grant and audit row, and a database an application brings up beside itself is a database nobody is backing up.

Procedure
  1. Install the operator. On OpenShift, find Keydra in OperatorHub and install it from there; nothing below this step is needed. Elsewhere:

    bash
    kubectl create namespace keydra-operator
    kubectl apply -f https://github.com/keydrahq/keydra-operator/releases/latest/download/crds.yaml
    kubectl apply -f https://github.com/keydrahq/keydra-operator/releases/latest/download/operator.yaml

    Two files rather than one, because they are applied by different people at different times: the custom resource definitions are cluster-wide and go in once, and the manager is a Deployment somebody upgrades.

  2. Make the Secret, in the namespace the installation will live in. The operator will not write it, and the difference from the chart is deliberate: a key put in a custom resource is readable by anybody who can get keydra in that namespace — a wider audience than anybody who can read Secrets, and the one least likely to have been thought about.

    bash
    kubectl create secret generic keydra \
      --from-literal=secret-key="$(openssl rand -base64 32)" \
      --from-literal=database-password='...'
    Important:Important

    Losing that key means losing every stored credential. Sharing it means sharing them.

  3. Describe the installation and apply it:

    yaml
    apiVersion: keydra.io/v1alpha1
    kind: Keydra
    metadata:
      name: keydra
    spec:
      database:
        url: postgresql://keydra-db:5432/keydra
      secret:
        name: keydra
      route:                      # <1>
        enabled: true
      proxy:                      # <2>
        enabled: true
        trusted: 10.0.0.0/8
    1 On OpenShift. Elsewhere use ingress.enabled with ingress.hosts, and set publicUrl to the address a browser will reach it at — a redirect URI is agreed with an identity provider in advance and has to match to the character.
    2 Under an Ingress or a Route there is always something in front. Told nothing, Keydra sees every sign-in as coming from the ingress controller: the checks that compare a sign-in with the ones before it then compare everybody with everybody, and the limit on attempts counts the whole cluster as one network. Naming the proxies is not optional — the API server refuses a resource that turns the switch on and names nobody.
Verification
  • The resource says whether it worked, so nothing else has to be read to find out:

    bash
    $ kubectl get keydra
    NAME     READY   REPLICAS   URL                                AGE
    keydra   True    1          https://keydra.apps.example.com    2m

    READY is False with a reason in kubectl describe keydra keydra while anything is wrong. The one to expect first is a Degraded condition naming a key missing from the Secret.

  • There are no accounts yet. Create the first administrator once, through an endpoint that stops working the moment there is one:

    bash
    curl -fsS -X POST https://keydra.apps.example.com/api/v1/auth/setup \
      -H 'Content-Type: application/json' \
      -d '{"username":"you","password":"..."}'
Declaring a target as a resource

This is the part a chart cannot do. A store that something else in the cluster created can be handed to Keydra by the same manifest that created it, and taken away by the same deletion — a profile that exists because a resource says so also stops existing when the resource does.

It needs an account for the operator to sign in as, because Keydra has no credential for a machine — no token, no service account — so the operator signs in the way a browser does and keeps the session. Give it an account of its own rather than a person’s, so the audit log says which changes were somebody typing and which were a resource being applied. Writing a connection profile is an administrator’s permission, so the account has to hold that role.

bash
kubectl create secret generic keydra-api \
  --from-literal=api-username=operator \
  --from-literal=api-password='...'

Name it on the installation, and then declare targets:

yaml
apiVersion: keydra.io/v1alpha1
kind: Keydra
metadata:
  name: keydra
spec:
  apiAccount:
    secretName: keydra-api
  # …
---
apiVersion: keydra.io/v1alpha1
kind: KeydraConnection
metadata:
  name: orders-cache
spec:
  keydraRef: keydra
  host: orders-redis
  port: 6379
  guarded: true
  passwordSecret:
    name: orders-redis
    key: password

keydraRef names an installation in the same namespace, and cross-namespace is deliberately not possible: it would let anybody who can create a resource in their own namespace add a target to somebody else’s console.

Note:Note

The operator will not adopt a profile it did not create. If a target of that name already exists in Keydra — because somebody added it through the interface — the resource is refused rather than taking ownership of it and then deleting it when the resource goes.

Converting an existing profile is therefore deliberate rather than tidying, and it costs something: deleting and recreating it changes the profile’s id, and both a grant whose scope is that connection and membership of a server group are held against that id. The people who could see that target will stop being able to.

Leaving the chart for the operator

The two produce the same objects with the same names and the same labels, which makes moving between them an adoption rather than a delete and a recreate. The steps are in the operator’s own documentation, because the parts that matter are about Helm rather than about Keydra: the release has to be told to leave its objects behind, and a Secret the chart rendered has to be copied into one the operator can be pointed at.

Configuring authentication before exposing Keydra#

Enforcement is on by default. deploy/keydra-prod.yaml includes it turned off so the manifest runs unchanged on a machine only you can reach — and the interface says "security off" on every page, because an open instance that looks secured is how one ends up exposed.

Procedure
  1. Decide how people will sign in. Either is enough on its own:

    • Local accounts. Nothing to configure. Create the first administrator on first run and invite the rest.

    • An identity provider. Configured while running, from the interface, rather than through environment variables — see Identity providers.

  2. Turn enforcement on by removing KEYDRA_SECURITY_ENABLED from the manifest, or setting it to true.

  3. Tell Keydra its own public address, so a provider’s redirect comes back to the right place:

    properties
    KEYDRA_PUBLIC_URL=https://keydra.example.com
  4. If Keydra runs behind a reverse proxy, say so, and name the proxies it should believe about the client’s address:

    properties
    KEYDRA_BEHIND_PROXY=true
    KEYDRA_TRUSTED_PROXIES=10.0.0.0/8
  5. Serve it over HTTPS. The session cookie is marked Secure in production, and a session cookie sent over plain HTTP is a session cookie on the wire.

Verification

Open Keydra in a private window. It offers a sign-in form or a provider button rather than the interface, and no page carries the "security off" notice.

Warning:Warning

KEYDRA_SECURITY_ENABLED=false admits everybody who can reach the address, with every permission. Use it for a demonstration or a machine only you can reach, and never on anything exposed.

Everything a deployment can set#

Every environment variable Keydra reads, grouped by the decision it belongs to, with the value a packaged runtime starts from. Anything with no default and no value is either off or absent.

Two groups need reading before the others. Required settings, because Keydra does not start without them. Enforcement, cookies and sessions, because their defaults are safe and turning one off is a decision rather than a tweak.

Required settings
Environment variable Default Description

KEYDRA_DB_URL

postgresql://localhost:5432/keydra

The reactive PostgreSQL URL, as postgresql://host:port/database. No JDBC prefix — the application is non-blocking and uses a reactive driver.

KEYDRA_DB_USERNAME

keydra

The database user.

KEYDRA_DB_PASSWORD

The database password.
Holds a secret. Never logged, never returned by the API.

KEYDRA_SECRET_KEY

The key that encrypts every stored credential — target passwords, tunnel keys, provider secrets, destination credentials. 32 random bytes, base64. There is no default: without it, nothing that was stored can be read.
Holds a secret. Never logged, never returned by the API.

Address and reverse proxy
Environment variable Default Description

KEYDRA_PUBLIC_URL

Where Keydra is, as a browser sees it. A provider’s redirect and the links in outgoing mail are built from this, so it has to be the address people actually use rather than the one the process binds to.

KEYDRA_BEHIND_PROXY

false

Whether to believe the X-Forwarded-* headers. On behind a reverse proxy, and only then: a deployment that trusts them with nothing in front is a deployment where a caller states their own address.

KEYDRA_TRUSTED_PROXIES

Which proxies may set those headers, as addresses or CIDR ranges. Without it, believing them means believing anybody.

KEYDRA_CSP

default-src 'self'; script-src 'self'; s…

The content security policy the browser is told to enforce. A property rather than a literal, so a deployment serving assets from elsewhere can widen the one directive it needs instead of turning the header off. Widening one means restating them all, which is the point: a policy is a whole or it is nothing.

KEYDRA_MAX_BODY_SIZE

25M

The largest request body accepted. This is what bounds a key import and a backup restore.

KEYDRA_ACCESS_LOG

true

Whether every request is logged. A request line carries a path, and a path here can name a key.

Enforcement, cookies and sessions
Environment variable Default Description

KEYDRA_SECURITY_ENABLED

true

Whether Keydra enforces who may do what. Turning it off admits everybody who can reach the address, with every permission, and every page then says so — an open instance that looks secured is how one ends up exposed.

KEYDRA_COOKIE_SECURE

true

Whether the session cookie is marked Secure. On in production: a session cookie sent over plain HTTP is a session cookie on the wire.

KEYDRA_SOCKET_ORIGIN_CHECK

true

Whether a WebSocket handshake must come from an origin Keydra recognizes. A socket is not covered by the same-origin policy the way a fetch is, so this is the check that replaces it.

KEYDRA_SOCKET_ORIGINS

The origins a WebSocket may be opened from, beyond the public URL. For a deployment where the interface is served from somewhere else.

KEYDRA_SIGN_IN_THROTTLE

true

Whether repeated sign-in failures are counted and refused. The limit is answered before the password hash, because Argon2id is slow on purpose and unlimited attempts consume the server’s memory as well as guessing a password.

KEYDRA_GEOIP_DATABASE

A GeoIP database, if one is available. Used to say where a sign-in came from when comparing it with the ones before it.

KEYDRA_SESSION_SWEEP_INTERVAL

1h

How often expired session rows are deleted. A session table nobody prunes is a table that grows for as long as the application runs.

KEYDRA_INVITATION_VALID_FOR

P7D

How long an invitation or password-reset link works. A link that works forever is a password with extra steps.

KEYDRA_PREVIOUS_SECRET_KEYS

Keys that may still be read but are no longer written. This is what makes a key rotation something other than an outage: the new key writes, the old ones keep decrypting what they wrote, and the re-encryption moves everything across while the instance is up.
Holds a secret. Never logged, never returned by the API.

Outbound address restrictions
Environment variable Default Description

KEYDRA_EGRESS_ALLOW_PRIVATE

true

Whether an address somebody typed may be on a private network. Usually yes: a webhook to an internal chat relay and an S3-compatible store on the same subnet are the ordinary cases. Link-local is refused regardless and has no setting.

KEYDRA_EGRESS_ALLOW_LOOPBACK

false

Whether an address somebody typed may point back at the machine Keydra runs on. Off outside development.

KEYDRA_EGRESS_ALLOWED_HOSTS

Hosts that are allowed regardless of the rules above. An escape hatch for the one internal address a deployment has to reach.

An OIDC provider configured at startup
Environment variable Default Description

KEYDRA_OIDC_URL

The issuer of a single OIDC provider configured at boot. Providers added in the interface are the supported path and need none of these four; this is for a deployment that configures one before there is anybody to sign in and add it.

KEYDRA_OIDC_CLIENT_ID

keydra

The client id at that provider.

KEYDRA_OIDC_SECRET

The client secret at that provider.
Holds a secret. Never logged, never returned by the API.

KEYDRA_OIDC_ROLES_CLAIM

realm_access/roles

Where the roles are in the token, as a path. A claim of viewer, operator or admin becomes a grant of that role on the instance.

More than one instance
Environment variable Default Description

KEYDRA_INSTANCE_ID

What this instance calls itself, in the About page, in every log line and on every metric it produces. Generated when it is not set.

KEYDRA_STORE_URL

redis://localhost:6481

Keydra’s own Redis, for the shared cache and for re-broadcasting notifications between instances. Never one of your targets: a cache living in a server somebody is browsing is a cache somebody empties with a bulk delete. Leave it unset for a single instance.

KEYDRA_LEASE_SECONDS

15

How long the leader lease is held before it has to be renewed. An instance that stops renewing loses it to whoever asks next, so this is also how long the work can be interrupted by a crash.

KEYDRA_RECONCILE_SECONDS

30

How often an instance checks whether it should be doing the leader work — claiming the lease if nobody holds it, and letting go of the schedules if it has lost it.

Mail server
Environment variable Default Description

KEYDRA_MAIL_HOST

The SMTP relay outgoing mail goes through — invitations, password resets, and email alert deliveries.

KEYDRA_MAIL_PORT

587

The relay port.

KEYDRA_MAIL_TLS

true

Whether to use TLS to the relay.

KEYDRA_MAIL_USERNAME

The account Keydra authenticates to the relay as.

KEYDRA_MAIL_API_KEY

The relay password or API key.
Holds a secret. Never logged, never returned by the API.

KEYDRA_MAIL_FROM

The address outgoing mail is sent from. Most relays refuse a message without one.

Backup and metrics storage
Environment variable Default Description

KEYDRA_BACKUP_DIR

backups

The directory a local backup destination writes inside. A destination names a directory relative to this, so no destination can be pointed at an arbitrary path on the machine.

KEYDRA_CLICKHOUSE_ENABLED

false

Whether readings are also written somewhere that survives a restart. Off by default: another service in a deployment is a real cost, and an instance that does not want one must not be told it needs one.

KEYDRA_CLICKHOUSE_URL

http://localhost:8123

The ClickHouse HTTP interface. HTTP rather than the JDBC driver, which is blocking — and this application is not.

KEYDRA_CLICKHOUSE_USER

default

The ClickHouse user.

KEYDRA_CLICKHOUSE_PASSWORD

The ClickHouse password.
Holds a secret. Never logged, never returned by the API.

Logs and traces
Environment variable Default Description

KEYDRA_OTLP_ENDPOINT

Where OpenTelemetry traces are exported. Setting it is what turns tracing on; there is no second flag.

KEYDRA_JSON_LOGS

true

JSON on the console instead of the human-readable format, so a log shipper does not have to parse a layout written for a person.

Note:Note

The same settings are listed alphabetically, with the property each one sets, in the configuration reference. This page groups them by what you are deciding; that one answers "what is this".

A complete example#

Every group at once, so nothing has to be guessed at. Most deployments set a fraction of it — the four under Required, an address, and whatever they actually run.

yaml
env:
  # --- Required -------------------------------------------------------------
  - name: KEYDRA_DB_URL
    value: postgresql://db.internal:5432/keydra
  - name: KEYDRA_DB_USERNAME
    value: keydra
  - name: KEYDRA_DB_PASSWORD
    valueFrom: { secretKeyRef: { name: keydra-db-password, key: keydra-db-password } }
  # 32 random bytes, base64. Losing it loses every stored credential.
  - name: KEYDRA_SECRET_KEY
    valueFrom: { secretKeyRef: { name: keydra-secret-key, key: keydra-secret-key } }

  # --- Address and reverse proxy --------------------------------------------
  - name: KEYDRA_PUBLIC_URL
    value: https://keydra.example.com
  - name: KEYDRA_BEHIND_PROXY
    value: "true"
  - name: KEYDRA_TRUSTED_PROXIES
    value: 10.0.0.0/8
  # Raise only if a key import or a backup restore is larger than this.
  - name: KEYDRA_MAX_BODY_SIZE
    value: 25M

  # --- Enforcement ----------------------------------------------------------
  # On by default. Off admits everybody who can reach the address.
  - name: KEYDRA_SECURITY_ENABLED
    value: "true"
  - name: KEYDRA_COOKIE_SECURE
    value: "true"

  # --- More than one instance ----------------------------------------------
  - name: KEYDRA_INSTANCE_ID
    value: keydra-a
  # Keydra's own Redis. Never one of your targets.
  - name: KEYDRA_STORE_URL
    value: redis://store.internal:6379

  # --- Outgoing mail --------------------------------------------------------
  - name: KEYDRA_MAIL_HOST
    value: smtp.example.com
  - name: KEYDRA_MAIL_PORT
    value: "587"
  - name: KEYDRA_MAIL_TLS
    value: "true"
  - name: KEYDRA_MAIL_USERNAME
    value: keydra
  - name: KEYDRA_MAIL_API_KEY
    valueFrom: { secretKeyRef: { name: keydra-mail-key, key: keydra-mail-key } }
  - name: KEYDRA_MAIL_FROM
    value: keydra@example.com

  # --- Metrics storage --------------------------------------------------------
  - name: KEYDRA_CLICKHOUSE_ENABLED
    value: "true"
  - name: KEYDRA_CLICKHOUSE_URL
    value: http://clickhouse.internal:8123
  - name: KEYDRA_CLICKHOUSE_USER
    value: keydra
  - name: KEYDRA_CLICKHOUSE_PASSWORD
    valueFrom: { secretKeyRef: { name: keydra-clickhouse, key: keydra-clickhouse } }

  # --- Observability --------------------------------------------------------
  - name: KEYDRA_OTLP_ENDPOINT
    value: http://otel-collector.internal:4317
  - name: KEYDRA_JSON_LOGS
    value: "true"
Important:Every secret comes from a secret

The four valueFrom entries above are not decoration. A password written into a manifest is a password in the repository, in the deployment history and in whatever prints the manifest — and KEYDRA_SECRET_KEY in particular is the one value that unlocks every other credential Keydra holds.

Settings this page does not list#

Keydra is a Quarkus application, so every Quarkus setting is available even when it has no KEYDRA_-prefixed name of its own. The mapping is Quarkus' own: upper-case the property and replace each non-alphanumeric character with an underscore, so quarkus.http.port is QUARKUS_HTTP_PORT.

Confirm that mapping against your Quarkus version before relying on it for a property with unusual characters in its name.

Ports and endpoints#

What Keydra listens on
Address Port What it is

/

8181

The interface and everything below. One port serves the API and the page that calls it.

/api/v1

8181

The REST API.

/graphql

8181

The GraphQL surface. GET is refused; introspection and the schema document are on in development and off in production.

/q/health, /q/health/live, /q/health/ready

8181

Health. The manifests use ready for readiness and live for liveness.

/q/metrics

8181

Prometheus.

/api/openapi

8181

The OpenAPI document.

/q/swagger-ui

8181

Interactive API browsing. Development profile only.

Development ports
What Host port Notes

Backend

8181

./mvnw quarkus:dev

Frontend dev server

9000

yarn dev, proxying /api and /graphql to the backend

Redis target

6479

From deploy/keydra-dev.yaml

Valkey target

6480

From deploy/keydra-dev.yaml

Redis for Keydra’s store

6481

Never one of the targets

PostgreSQL

5442

Keydra’s own database

ClickHouse

8223

Optional readings store

The host ports are shifted off the defaults deliberately, so the pod starts on a machine that already runs a Redis or a PostgreSQL. Inside the pod they keep their canonical numbers.

Building the image from source#

Only if you are changing Keydra. A deployment should use the published images.

Prerequisites
  • A checkout of the Keydra repository.

  • Podman.

Procedure
bash
podman build -t localhost/keydra:dev -f Containerfile .

The Containerfile in the repository builds the standalone image in three stages: the frontend with Node, the backend with Maven — with the built frontend copied into src/main/resources/META-INF/resources — and a JRE image that carries neither toolchain.

Verification
bash
podman run --rm -p 8181:8181 \
  -e KEYDRA_DB_URL=postgresql://host.containers.internal:5442/keydra \
  -e KEYDRA_DB_USERNAME=keydra \
  -e KEYDRA_DB_PASSWORD=keydra \
  -e KEYDRA_SECRET_KEY="$(openssl rand -base64 32)" \
  localhost/keydra:dev
Note:The image runs as a non-root user

It declares USER keydra and reads the container’s own memory limit rather than the host’s, so a limited container does not size its heap for a machine it cannot use. Tests are not run during the image build — they need containers, which a build container does not have; CI runs them.

Edit this page