diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000000..a816ae62fb3b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +# Use the official Ory Kratos image pinned to a secure digest +FROM oryd/kratos@sha256:e8014c6c58b68e9d8bea4160d3e271b0d05b3db221af379afb6798b603e88ee9 + +# Copy your configuration files into the container +COPY ./config/kratos.yml /etc/config/kratos/kratos.yml +COPY ./config/identity.schema.json /etc/config/kratos/identity.schema.json + +# Copy and setup entrypoint script for Railway deployment with execute permissions +COPY --chmod=755 entrypoint.sh /entrypoint.sh + +# Use entrypoint script that handles migrations and starts Kratos +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.railway.md b/README.railway.md new file mode 100644 index 000000000000..fc62c487561f --- /dev/null +++ b/README.railway.md @@ -0,0 +1,239 @@ +# Deploy Ory Kratos on Railway + +This guide helps you deploy Ory Kratos identity and user management system on Railway with automatic database migrations. + +## Quick Deploy + +Deploy Ory Kratos to Railway with one click: + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new) + +> **Note**: Once you've set up your deployment, you can create a Railway template to enable one-click deployment for others. The template button will link to your published Railway template. + +This will automatically: +- 🚀 Deploy the Kratos service +- 🗄️ Provision a PostgreSQL database +- 🔄 Run database migrations automatically +- 🔐 Generate secure secrets +- 🌐 Create a public domain + +## Manual Deployment + +### Prerequisites + +- A [Railway account](https://railway.app/) +- Railway CLI installed (optional): `npm i -g @railway/cli` + +### Step 1: Create a New Project + +1. Go to [Railway](https://railway.app/) +2. Click "New Project" +3. Choose "Deploy from GitHub repo" +4. Select this repository + +### Step 2: Add PostgreSQL Database + +1. In your Railway project, click "New" +2. Select "Database" +3. Choose "PostgreSQL" +4. Railway will automatically provide these environment variables: + - `POSTGRES_HOST` + - `POSTGRES_PORT` + - `POSTGRES_USER` + - `POSTGRES_PASSWORD` + - `POSTGRES_DATABASE` + +### Step 3: Configure Environment Variables + +Add the following environment variables to your Kratos service: + +#### Required Secrets + +Generate secure secrets using OpenSSL: + +```bash +# Generate COOKIE_SECRET (32 bytes hex = 64 characters) +openssl rand -hex 32 + +# Generate CIPHER_SECRET (32 bytes hex = 64 characters) +openssl rand -hex 32 +``` + +Add these to Railway: +- `COOKIE_SECRET`: Your generated cookie secret +- `CIPHER_SECRET`: Your generated cipher secret + +#### Automatic Variables + +Railway automatically provides: +- `RAILWAY_STATIC_URL`: Your public domain (e.g., `kratos-production.up.railway.app`) + +The entrypoint script automatically constructs the database DSN from the PostgreSQL variables. + +### Step 4: Deploy + +1. Railway will automatically build and deploy your service +2. The entrypoint script will: + - Wait for PostgreSQL to be ready + - Run database migrations automatically + - Start the Kratos server +3. Your Kratos instance will be available at your Railway domain + +## Service Endpoints + +Once deployed, you'll have access to: + +- **Public API**: `https://${RAILWAY_STATIC_URL}` (Railway maps your domain to port 4433 internally) +- **Admin API**: Internal only, accessible at `http://localhost:4434` or between Railway services +- **Health Check**: `https://${RAILWAY_STATIC_URL}/health/ready` + +Note: Railway automatically handles port mapping. The public API is accessible via your Railway domain, while the admin API is typically kept internal for security. + +## Configuration + +### Database Connection + +The entrypoint script automatically constructs the PostgreSQL DSN from Railway's environment variables: + +```bash +postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DATABASE}?sslmode=require +``` + +If you prefer to use a custom `DATABASE_URL`, the script will use that instead. + +### Identity Schema + +The default identity schema (`/etc/config/kratos/identity.schema.json`) supports: +- Email and password authentication +- Email verification +- Password recovery +- User profile with first and last name + +You can customize this by modifying `config/identity.schema.json` in your repository. + +### Authentication Methods + +Enabled by default: +- ✅ Password authentication +- ✅ TOTP (Time-based One-Time Password) +- ✅ Recovery codes (lookup secrets) +- ✅ Email verification codes +- ✅ Password recovery codes + +### Self-Service Flows + +All self-service flows are pre-configured: +- Registration: `https://${RAILWAY_STATIC_URL}/registration` +- Login: `https://${RAILWAY_STATIC_URL}/login` +- Recovery: `https://${RAILWAY_STATIC_URL}/recovery` +- Verification: `https://${RAILWAY_STATIC_URL}/verification` +- Settings: `https://${RAILWAY_STATIC_URL}/settings` +- Error: `https://${RAILWAY_STATIC_URL}/error` + +**Note**: These URLs expect a frontend application. You'll need to build UI pages for these flows or use Ory's hosted UI. + +## Automatic Migrations + +The entrypoint script (`entrypoint.sh`) handles database migrations automatically: + +1. **Waits for PostgreSQL**: Retries connection up to 30 times (approximately 60+ seconds total with 2-second intervals) +2. **Shows current status**: Displays migration status before running +3. **Runs migrations**: Executes `kratos migrate sql up -e --yes` +4. **Confirms success**: Shows migration status after completion +5. **Starts server**: Launches Kratos with `serve all` command + +### Migration Logs + +Check your Railway deployment logs to see migration progress: + +``` +🚀 Starting Ory Kratos setup... +✅ Using Railway PostgreSQL +📊 Database connection configured +⏳ Waiting for PostgreSQL to be ready... +✅ Database is ready! +📋 Current migration status: +... +🔄 Running database migrations... +... +🎉 Migrations completed successfully! +🚀 Starting Kratos server... +``` + +## Troubleshooting + +### Database Connection Issues + +If migrations fail: + +1. Check PostgreSQL service is running in Railway +2. Verify environment variables are set correctly +3. Check Railway logs for connection errors +4. Ensure PostgreSQL service is linked to Kratos service + +### Secret Generation + +Always use strong secrets: + +```bash +# Good: 32 bytes = 64 hex characters +openssl rand -hex 32 + +# Bad: Short or predictable secrets +# Don't use: "mysecret123" or other simple strings +``` + +### Health Check Failing + +The health check endpoint is `/health/ready`. If it fails: + +1. Check Kratos logs in Railway +2. Verify database migrations completed successfully +3. Ensure both ports 4433 and 4434 are accessible +4. Check that `RAILWAY_STATIC_URL` is set correctly + +### Viewing Logs + +In Railway: +1. Open your project +2. Click on the Kratos service +3. Go to the "Deployments" tab +4. Click on the latest deployment +5. View logs in real-time + +## Security Considerations + +1. **Secrets**: Railway automatically generates secure secrets. Store them safely. +2. **HTTPS**: Railway provides HTTPS by default for your domain. +3. **Database**: PostgreSQL runs in a private network, not publicly accessible. +4. **CORS**: CORS is enabled. Configure `allowed_return_urls` in `config/kratos.yml` for your specific domains. +5. **Admin API**: The admin API (port 4434) should be restricted to internal services only. + +## Updating Configuration + +To update Kratos configuration: + +1. Modify `config/kratos.yml` in your repository +2. Commit and push changes +3. Railway will automatically redeploy +4. Migrations will run again (safely, they're idempotent) + +## Support + +- [Ory Kratos Documentation](https://www.ory.sh/docs/kratos) +- [Railway Documentation](https://docs.railway.app/) +- [Ory Community](https://github.com/ory/kratos/discussions) + +## Next Steps + +After deployment: + +1. **Build a UI**: Create frontend pages for the self-service flows +2. **Configure Email**: Set up SMTP for email verification and recovery +3. **Add OAuth**: Configure social login providers if needed +4. **Customize Schema**: Modify identity schema for your use case +5. **Set up Monitoring**: Use Railway's monitoring features + +## License + +This deployment configuration is part of the Ory Kratos project. See the main repository for license information. diff --git a/config/identity.schema.json b/config/identity.schema.json new file mode 100644 index 000000000000..1a137875666e --- /dev/null +++ b/config/identity.schema.json @@ -0,0 +1,49 @@ +{ + "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "traits": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "title": "E-Mail", + "minLength": 3, + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + }, + "verification": { + "via": "email" + }, + "recovery": { + "via": "email" + } + } + }, + "name": { + "type": "object", + "properties": { + "first": { + "title": "First Name", + "type": "string" + }, + "last": { + "title": "Last Name", + "type": "string" + } + } + } + }, + "required": [ + "email" + ], + "additionalProperties": false + } + } +} diff --git a/config/kratos.yml b/config/kratos.yml new file mode 100644 index 000000000000..7bbe59a1a236 --- /dev/null +++ b/config/kratos.yml @@ -0,0 +1,101 @@ +version: v0.13.0 + +dsn: env://DSN + +serve: + public: + base_url: https://${RAILWAY_STATIC_URL} + port: 4433 + cors: + enabled: true + admin: + base_url: http://0.0.0.0:4434 + port: 4434 + +selfservice: + default_browser_return_url: https://${RAILWAY_STATIC_URL}/welcome + allowed_return_urls: + - https://${RAILWAY_STATIC_URL} + + methods: + password: + enabled: true + totp: + config: + issuer: Kratos + enabled: true + lookup_secret: + enabled: true + link: + enabled: true + code: + enabled: true + + flows: + error: + ui_url: https://${RAILWAY_STATIC_URL}/error + + settings: + ui_url: https://${RAILWAY_STATIC_URL}/settings + privileged_session_max_age: 15m + required_aal: highest_available + + recovery: + enabled: true + ui_url: https://${RAILWAY_STATIC_URL}/recovery + use: code + + verification: + enabled: true + ui_url: https://${RAILWAY_STATIC_URL}/verification + use: code + after: + default_browser_return_url: https://${RAILWAY_STATIC_URL}/welcome + + logout: + after: + default_browser_return_url: https://${RAILWAY_STATIC_URL}/login + + login: + ui_url: https://${RAILWAY_STATIC_URL}/login + lifespan: 10m + + registration: + lifespan: 10m + ui_url: https://${RAILWAY_STATIC_URL}/registration + after: + password: + hooks: + - hook: session + - hook: show_verification_ui + +log: + level: info + format: json + +secrets: + cookie: + - ${COOKIE_SECRET} + cipher: + - ${CIPHER_SECRET} + +ciphers: + algorithm: xchacha20-poly1305 + +hashers: + algorithm: bcrypt + bcrypt: + cost: 12 + +identity: + default_schema_id: default + schemas: + - id: default + url: file:///etc/config/kratos/identity.schema.json + +courier: + smtp: + connection_uri: "" + +feature_flags: + use_continue_with_transitions: true diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 000000000000..b051d53cb5f3 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,62 @@ +#!/bin/sh +set -e + +echo "🚀 Starting Ory Kratos setup..." + +# Construct DSN from Railway PostgreSQL variables +if [ -n "$POSTGRES_HOST" ]; then + export DSN="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DATABASE}?sslmode=require" + echo "✅ Using Railway PostgreSQL" +elif [ -n "$DATABASE_URL" ]; then + export DSN="$DATABASE_URL" + echo "✅ Using DATABASE_URL" +else + echo "❌ ERROR: No database configuration found" + exit 1 +fi + +echo "📊 Database connection configured" + +# Wait for database to be ready +echo "⏳ Waiting for PostgreSQL to be ready..." +MAX_RETRIES=30 +RETRY_COUNT=0 +RETRY_INTERVAL=2 + +until kratos migrate sql status -e 2>/dev/null || [ $RETRY_COUNT -eq $MAX_RETRIES ]; do + RETRY_COUNT=$((RETRY_COUNT + 1)) + echo " Attempt $RETRY_COUNT/$MAX_RETRIES - Database not ready yet..." + sleep $RETRY_INTERVAL +done + +if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then + echo "❌ ERROR: Database did not become ready after $MAX_RETRIES attempts" + exit 1 +fi + +echo "✅ Database is ready!" + +# Show migration status before running migrations +echo "" +echo "📋 Current migration status:" +if ! kratos migrate sql status -e 2>&1; then + echo "⚠️ Note: Migration status check failed (this is expected for a new database)" +fi + +# Run migrations +echo "" +echo "🔄 Running database migrations..." +kratos migrate sql up -e --yes + +# Show migration status after running migrations +echo "" +echo "📋 Migration status after update:" +kratos migrate sql status -e + +echo "" +echo "🎉 Migrations completed successfully!" +echo "🚀 Starting Kratos server..." +echo "" + +# Start Kratos server +exec kratos serve all -c /etc/config/kratos/kratos.yml diff --git a/railway.json b/railway.json new file mode 100644 index 000000000000..c456e69575d0 --- /dev/null +++ b/railway.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "DOCKERFILE", + "dockerfilePath": "Dockerfile" + }, + "deploy": { + "numReplicas": 1, + "healthcheckPath": "/health/ready", + "healthcheckTimeout": 30, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 10 + } +}