Skip to main content
Ory Kratos uses automatic database migrations to keep your schema up to date. This guide explains how to run and manage migrations.

Migration overview

Kratos stores migration state in the database and automatically applies necessary schema changes. Migrations are idempotent and can be run multiple times safely.

Running migrations

Using the CLI

The primary way to run migrations is using the kratos migrate sql command:
kratos migrate sql -e --yes \
  --config /path/to/kratos.yml
-e, --read-from-env
boolean
Read database DSN from environment variable DSN
--yes
boolean
Automatically confirm migration execution
-c, --config
string
Path to configuration file

Environment variable

Set the DSN via environment variable:
export DSN="postgres://kratos:secret@postgres:5432/kratos?sslmode=require"
kratos migrate sql -e --yes

Migration commands

Apply all pending migrations:
kratos migrate sql -e --yes
This is the most common command and is used for both initial setup and upgrades.

Initial database setup

For a new deployment:
1

Create database

Create an empty database:
CREATE DATABASE kratos;
CREATE USER kratos WITH PASSWORD 'secure-password';
GRANT ALL PRIVILEGES ON DATABASE kratos TO kratos;
2

Set DSN

Configure the database connection:
export DSN="postgres://kratos:secure-password@localhost:5432/kratos?sslmode=require"
3

Run migrations

Apply the schema:
kratos migrate sql -e --yes
4

Verify

Check that migrations succeeded:
kratos migrate sql status -e

Upgrading Kratos

When upgrading to a new Kratos version:
1

Backup database

Always backup before upgrading:
pg_dump -U kratos -h localhost kratos > backup-$(date +%Y%m%d).sql
2

Stop Kratos

Stop all running Kratos instances to prevent conflicts during migration.
3

Run migrations

Apply new migrations with the updated binary:
kratos migrate sql -e --yes
4

Start Kratos

Start Kratos with the new version:
kratos serve -c /path/to/kratos.yml

Docker deployments

Run migrations in Docker before starting the main service:
version: '3.7'

services:
  kratos-migrate:
    image: oryd/kratos:v25.4.0
    environment:
      - DSN=postgres://kratos:secret@postgres:5432/kratos?sslmode=require
    volumes:
      - ./config:/etc/config/kratos
    command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
    restart: on-failure
    networks:
      - intranet
  
  kratos:
    depends_on:
      - kratos-migrate
    image: oryd/kratos:v25.4.0
    # ... rest of configuration

Kubernetes deployments

Use a Kubernetes Job to run migrations:
apiVersion: batch/v1
kind: Job
metadata:
  name: kratos-migrate-{{ .Release.Revision }}
  namespace: kratos
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
    "helm.sh/hook-weight": "1"
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  backoffLimit: 10
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: migrate
        image: oryd/kratos:v25.4.0
        command:
          - kratos
        args:
          - migrate
          - sql
          - -e
          - --yes
          - --config
          - /etc/config/kratos.yml
        env:
        - name: DSN
          valueFrom:
            secretKeyRef:
              name: kratos-secrets
              key: dsn
        volumeMounts:
        - name: config
          mountPath: /etc/config
      volumes:
      - name: config
        configMap:
          name: kratos-config

CI/CD integration

GitHub Actions

name: Deploy Kratos

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Run migrations
        env:
          DSN: ${{ secrets.KRATOS_DSN }}
        run: |
          docker run --rm \
            -e DSN="$DSN" \
            -v $(pwd)/config:/etc/config \
            oryd/kratos:v25.4.0 \
            migrate sql -e --yes
      
      - name: Deploy Kratos
        run: |
          # Your deployment script

GitLab CI

migrate:
  stage: deploy
  image: oryd/kratos:v25.4.0
  script:
    - kratos migrate sql -e --yes
  only:
    - main
  environment:
    name: production
  variables:
    DSN: $KRATOS_DSN

Migration safety

Always follow these safety practices when running migrations:
1

Backup first

Always create a database backup before running migrations.
2

Test in staging

Run migrations in a staging environment first to identify issues.
3

Stop writes

Stop all Kratos instances before running migrations to prevent conflicts.
4

Monitor

Watch migration output for errors or warnings.
5

Verify

Check migration status after completion:
kratos migrate sql status -e

Troubleshooting

If migrations hang or timeout:
  1. Check for other running Kratos instances
  2. Look for stale migration locks in the database
  3. Increase timeout (if needed)
Clear stale locks (use with caution):
-- PostgreSQL
DELETE FROM schema_migration WHERE dirty = true;
If a migration fails:
  1. Review error messages in output
  2. Check database logs
  3. Restore from backup if needed
  4. Contact Ory support with error details
Ensure the database user has proper permissions:
-- PostgreSQL
GRANT ALL PRIVILEGES ON DATABASE kratos TO kratos;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO kratos;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO kratos;
Verify DSN format and network connectivity:
# Test connection
psql "$DSN"

# Verify DSN is correct
echo $DSN

Migration versioning

Kratos migrations are tied to release versions. When upgrading:
  • Patch versions (e.g., v25.4.0 → v25.4.1): Usually no new migrations
  • Minor versions (e.g., v25.4.0 → v25.5.0): May include new migrations
  • Major versions (e.g., v25.0.0 → v26.0.0): Often include breaking migrations
Always check the changelog before upgrading.

Next steps

Configuration

Configure Kratos for production

Database setup

Learn about database options

Build docs developers (and LLMs) love