DEV Community

Cover image for Deploying Ory Kratos Open-Source Identity and User Management System on Ubuntu 24.04
Sanskriti Harmukh for Vultr

Posted on with Aashish Chaurasiya • Originally published at docs.vultr.com

Deploying Ory Kratos Open-Source Identity and User Management System on Ubuntu 24.04

Ory Kratos is an open-source, API-first identity and user management system handling registration, login, recovery, verification, and session management with a self-service UI. This guide deploys Kratos using Docker Compose with PostgreSQL, the self-service UI Node, and Traefik handling automatic HTTPS for the public API. By the end, you'll have Kratos managing identities and sessions for users registering through your domain over HTTPS.

Prerequisite: SMTP credentials are required for verification and recovery emails. The admin API stays bound to 127.0.0.1 on purpose — never expose it publicly.


Set Up the Directory Structure

1. Create the project directories:

$ mkdir -p ~/ory-kratos/{config,data/postgres}
$ cd ~/ory-kratos
Enter fullscreen mode Exit fullscreen mode

2. Create the environment file:

$ nano .env
Enter fullscreen mode Exit fullscreen mode
DOMAIN=kratos.example.com
LETSENCRYPT_EMAIL=admin@example.com
KRATOS_VERSION=v26.2.0
POSTGRES_USER=kratos
POSTGRES_PASSWORD=EXAMPLE_DB_PASSWORD
POSTGRES_DB=kratosdb
LOG_LEVEL=info
Enter fullscreen mode Exit fullscreen mode

3. Create the identity schema — defines the user fields (email + name) and how the email maps to login, recovery, and verification:

$ nano config/identity.schema.json
Enter fullscreen mode Exit fullscreen mode

Use the schema described in the Vultr Docs walkthrough — email is the login identifier with password auth; name is a free-text trait.

4. Create the Kratos configuration — public/admin API URLs, password policy (12-char minimum + HaveIBeenPwned), session lifetimes, self-service flows, SMTP courier:

$ nano config/kratos.yml
Enter fullscreen mode Exit fullscreen mode

Fill in the full configuration from the source article. Key points to keep consistent with the stack below:

  • Public API listens on the internal port and is fronted by Traefik on ${DOMAIN}.
  • Admin API listens on 127.0.0.1:4434 only — used by tooling on the host.
  • The DSN points at the postgres service (postgres://kratos:...@postgres:5432/kratosdb?sslmode=disable).
  • The courier section uses your SMTP provider for verification mail.

Deploy with Docker Compose

1. Create the Compose manifest:

$ nano docker-compose.yml
Enter fullscreen mode Exit fullscreen mode
services:
  traefik:
    image: traefik:v3.6
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    restart: unless-stopped

  postgres:
    image: postgres:16
    container_name: kratos-postgres
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}", "-d", "${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  kratos-migrate:
    image: oryd/kratos:${KRATOS_VERSION}
    container_name: kratos-migrate
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./config:/etc/config/kratos:ro
    environment:
      DSN: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable"
    command: migrate -c /etc/config/kratos/kratos.yml sql -e --yes

  kratos:
    image: oryd/kratos:${KRATOS_VERSION}
    container_name: kratos
    depends_on:
      kratos-migrate:
        condition: service_completed_successfully
    volumes:
      - ./config:/etc/config/kratos:ro
    environment:
      DSN: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable"
      LOG_LEVEL: ${LOG_LEVEL}
    command: serve -c /etc/config/kratos/kratos.yml --watch-courier
    ports:
      - "127.0.0.1:4434:4434"
    expose:
      - "4433"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.kratos.rule=Host(`${DOMAIN}`) && PathPrefix(`/.ory/kratos/public`)"
      - "traefik.http.routers.kratos.entrypoints=websecure"
      - "traefik.http.routers.kratos.tls.certresolver=letsencrypt"
      - "traefik.http.services.kratos.loadbalancer.server.port=4433"
    restart: unless-stopped

  kratos-ui:
    image: oryd/kratos-selfservice-ui-node:${KRATOS_VERSION}
    container_name: kratos-ui
    depends_on:
      - kratos
    environment:
      KRATOS_PUBLIC_URL: https://${DOMAIN}/.ory/kratos/public
      KRATOS_BROWSER_URL: https://${DOMAIN}/.ory/kratos/public
    expose:
      - "3000"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.kratos-ui.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.kratos-ui.entrypoints=websecure"
      - "traefik.http.routers.kratos-ui.tls.certresolver=letsencrypt"
      - "traefik.http.services.kratos-ui.loadbalancer.server.port=3000"
    restart: unless-stopped
Enter fullscreen mode Exit fullscreen mode

2. Start the stack:

$ docker compose up -d
$ docker compose ps -a
$ docker compose logs
Enter fullscreen mode Exit fullscreen mode

Health Checks

1. Probe the admin API on the loopback:

$ docker compose exec kratos wget -qO- http://127.0.0.1:4434/health/alive
Enter fullscreen mode Exit fullscreen mode

2. Probe the public API over HTTPS:

$ curl -s https://kratos.example.com/.ory/kratos/public/health/alive
Enter fullscreen mode Exit fullscreen mode

3. Confirm the self-service UI loads:

$ curl -s -o /dev/null -w "%{http_code}" https://kratos.example.com/
Enter fullscreen mode Exit fullscreen mode

Register and Sign In via the API

1. Open a registration flow:

$ curl -s https://kratos.example.com/.ory/kratos/public/self-service/registration/api
Enter fullscreen mode Exit fullscreen mode

Capture the id from the response — it's the FLOW_ID below.

2. Submit the registration:

$ curl -s -X POST \
    "https://kratos.example.com/.ory/kratos/public/self-service/registration?flow=FLOW_ID" \
    -H 'Content-Type: application/json' \
    -d '{
      "method": "password",
      "password": "YOUR-PASSWORD",
      "traits": {
        "email": "YOUR-EMAIL",
        "name": { "first": "FIRST-NAME", "last": "LAST-NAME" }
      }
    }'
Enter fullscreen mode Exit fullscreen mode

3. List identities through the admin API:

$ docker compose exec kratos wget -qO- http://127.0.0.1:4434/admin/identities
Enter fullscreen mode Exit fullscreen mode

4. Sign in:

$ curl -s https://kratos.example.com/.ory/kratos/public/self-service/login/api
$ curl -s -X POST \
    "https://kratos.example.com/.ory/kratos/public/self-service/login?flow=FLOW_ID" \
    -H 'Content-Type: application/json' \
    -d '{
      "method": "password",
      "identifier": "YOUR-EMAIL",
      "password": "YOUR-PASSWORD"
    }'
Enter fullscreen mode Exit fullscreen mode

5. Verify the session:

$ curl -s https://kratos.example.com/.ory/kratos/public/sessions/whoami \
    -H "X-Session-Token: SESSION_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Next Steps

Kratos is running with PostgreSQL persistence, the self-service UI, and HTTPS in front of the public API. From here you can:

  • Add social sign-in (Google, GitHub, Apple) through the OIDC provider list
  • Wire Kratos sessions to other services via Ory Oathkeeper or forward-auth
  • Use Ory Hydra alongside Kratos to expose OpenID Connect to client apps

For the full guide with additional tips, visit the original article on Vultr Docs.

Top comments (0)