Deployment

Production deployment with Dokploy and Docker

Deployment

This guide covers deploying the backend with Dokploy. The CI/CD pipeline builds a production Docker image and triggers deployment via a webhook.

Prerequisites

  • A server with Dokploy installed
  • A domain name pointed to your server
  • The CI/CD pipeline configured (see CI/CD)

Step-by-Step Setup

1. Create the Dokploy Application

In the Dokploy dashboard:

  1. Create a new application
  2. Set the source to Docker (from GitHub Container Registry)
  3. Configure the image: ghcr.io/{owner}/{repo}:main
  4. Set up a domain with HTTPS

2. Create the Database

Use the built-in Dokploy CLI command:

docker compose exec php bin/console app:dokploy

This interactive command helps you create a PostgreSQL database service and configure deployment settings.

3. Configure Environment Variables

Set these in Dokploy application settings:

APP_ENV=prod
APP_SECRET=<random-string>
DATABASE_URL=postgresql://user:password@host:5432/dbname
JWT_PASSPHRASE=<your-passphrase>

STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_SUCCESS_URL=https://yourdomain.com/payment/success
STRIPE_CANCEL_URL=https://yourdomain.com/payment/cancel

EMAIL_SENDER_EMAIL=noreply@yourdomain.com
EMAIL_SENDER_NAME=Your App Name
BREVO_API_KEY=xkeysib-...

EMAIL_VALIDATION_REDIRECT_URL=https://yourdomain.com/auth/email-verified
FRONTEND_RESET_PASSWORD_URL=https://yourdomain.com/auth/reset-password

FIREBASE_PROJECT_ID=your-firebase-project-id
CORS_ALLOW_ORIGIN=^https://yourdomain\.com$

4. Mount JWT Certificates

JWT keys need to persist between deployments. You can use the built-in Dokploy CLI command:

docker compose exec php bin/console app:dokploy

Or mount the certificates manually in Dokploy:

  1. Go to your service → AdvancedVolumes
  2. Click Add Volume and select File Mount
  3. Mount the private key:
    • Open your local config/jwt/private.pem and paste its content into the File Content field
    • Set Filename to private.pem
    • Set Mount Path to /app/config/jwt/private.pem
  4. Repeat for the public key:
    • Open your local config/jwt/public.pem and paste its content
    • Set Filename to public.pem
    • Set Mount Path to /app/config/jwt/public.pem

5. Set Up the Deployment Webhook

  1. In Dokploy → application → Settings → Webhooks, copy the webhook URL
  2. Add it as a GitHub secret: DOKPLOY_WEBHOOK_URL

6. Initial Setup Commands

After the first deployment, run inside the production container:

# Run database migrations
bin/console doctrine:migrations:migrate --no-interaction

# Generate JWT keys (first time only)
# Make sure JWT_PASSPHRASE is set in your environment variables (step 3) before running this
bin/console lexik:jwt:generate-keypair

# Sync Stripe plans
bin/console app:stripe:sync-plans

# Create an admin user (register via API first, then promote)
bin/console app:user:promote-admin admin@yourdomain.com

7. Configure Stripe Webhook

Configure the Stripe webhook endpoint for production — see Subscription & Payments for detailed instructions.

Deployment Architecture

GitHub (push to main)
    │
    ├── Backend CI/CD
    │   └── Test → Build → Push to GHCR → Dokploy webhook → Deploy
    │
    └── Frontend CI/CD
        └── Test → Build → Push to GHCR → Dokploy webhook → Deploy

Dokploy Server
    ├── Backend (FrankenPHP) ← https://api.yourdomain.com
    ├── Frontend (Node.js)   ← https://yourdomain.com
    ├── PostgreSQL
    └── Redis

Both services are deployed as Docker containers managed by Dokploy, with automatic HTTPS via Let's Encrypt.

Installing Dokploy

Dokploy is a self-hosted deployment platform. Install it on your server:

curl -sSL https://dokploy.com/install.sh | sh

Access the Dokploy dashboard at https://your-server-ip:3000.

DNS Configuration

RecordDomainTarget
Ayourdomain.comYour server IP
Aapi.yourdomain.comYour server IP

Dokploy handles HTTPS certificates automatically via Let's Encrypt.

Production Checklist

Verify everything before going live.

Backend

  • APP_ENV=prod is set
  • APP_SECRET is a unique random string (not the default)
  • DATABASE_URL points to the production database
  • JWT keys are generated and persisted via volume mount
  • JWT_PASSPHRASE matches the passphrase used to generate keys
  • Database migrations are applied
  • An admin user is created

Stripe

  • STRIPE_SECRET_KEY uses a live key (sk_live_...)
  • STRIPE_WEBHOOK_SECRET is set from the production webhook
  • Stripe webhook is configured with all 5 required events
  • STRIPE_SUCCESS_URL points to https://yourdomain.com/payment/success
  • STRIPE_CANCEL_URL points to https://yourdomain.com/payment/cancel
  • Plans are synced (bin/console app:stripe:sync-plans)

Email

  • BREVO_API_KEY is set with a valid key
  • EMAIL_SENDER_EMAIL uses a verified sender domain in Brevo
  • Brevo templates are created with correct IDs
  • Test emails are sending correctly

Firebase

  • FIREBASE_PROJECT_ID is set on the backend
  • Production domain is added to Firebase authorized domains

Cross-Project

  • CORS_ALLOW_ORIGIN on backend matches the frontend domain
  • EMAIL_VALIDATION_REDIRECT_URL points to frontend /auth/email-verified
  • FRONTEND_RESET_PASSWORD_URL points to frontend /auth/reset-password
  • All redirect URLs use https://

Infrastructure

  • HTTPS is configured on the API domain
  • DNS records point to the server
  • GitHub secrets are set for CI/CD (DOKPLOY_WEBHOOK_URL)
  • CI/CD pipeline passes (test → build → deploy)

Smoke Test

After deployment, verify these flows work end-to-end:

  1. Landing page loads at https://yourdomain.com
  2. Register a new user
  3. Check welcome email arrives (via Brevo)
  4. Login with the new user
  5. Complete a Stripe test checkout
  6. Verify subscription appears on profile page
  7. Test forgot password flow
  8. Test social login (if configured)
  9. Access admin panel at https://api.yourdomain.com/admin