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:
- Create a new application
- Set the source to Docker (from GitHub Container Registry)
- Configure the image:
ghcr.io/{owner}/{repo}:main - 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:
- Go to your service → Advanced → Volumes
- Click Add Volume and select File Mount
- Mount the private key:
- Open your local
config/jwt/private.pemand paste its content into the File Content field - Set Filename to
private.pem - Set Mount Path to
/app/config/jwt/private.pem
- Open your local
- Repeat for the public key:
- Open your local
config/jwt/public.pemand paste its content - Set Filename to
public.pem - Set Mount Path to
/app/config/jwt/public.pem
- Open your local
5. Set Up the Deployment Webhook
- In Dokploy → application → Settings → Webhooks, copy the webhook URL
- 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
| Record | Domain | Target |
|---|---|---|
| A | yourdomain.com | Your server IP |
| A | api.yourdomain.com | Your server IP |
Dokploy handles HTTPS certificates automatically via Let's Encrypt.
Production Checklist
Verify everything before going live.
Backend
-
APP_ENV=prodis set -
APP_SECRETis a unique random string (not the default) -
DATABASE_URLpoints to the production database - JWT keys are generated and persisted via volume mount
-
JWT_PASSPHRASEmatches the passphrase used to generate keys - Database migrations are applied
- An admin user is created
Stripe
-
STRIPE_SECRET_KEYuses a live key (sk_live_...) -
STRIPE_WEBHOOK_SECRETis set from the production webhook - Stripe webhook is configured with all 5 required events
-
STRIPE_SUCCESS_URLpoints tohttps://yourdomain.com/payment/success -
STRIPE_CANCEL_URLpoints tohttps://yourdomain.com/payment/cancel - Plans are synced (
bin/console app:stripe:sync-plans)
-
BREVO_API_KEYis set with a valid key -
EMAIL_SENDER_EMAILuses a verified sender domain in Brevo - Brevo templates are created with correct IDs
- Test emails are sending correctly
Firebase
-
FIREBASE_PROJECT_IDis set on the backend - Production domain is added to Firebase authorized domains
Cross-Project
-
CORS_ALLOW_ORIGINon backend matches the frontend domain -
EMAIL_VALIDATION_REDIRECT_URLpoints to frontend/auth/email-verified -
FRONTEND_RESET_PASSWORD_URLpoints 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:
- Landing page loads at
https://yourdomain.com - Register a new user
- Check welcome email arrives (via Brevo)
- Login with the new user
- Complete a Stripe test checkout
- Verify subscription appears on profile page
- Test forgot password flow
- Test social login (if configured)
- Access admin panel at
https://api.yourdomain.com/admin