server package through Dokploy. The server is an independent repository and receives its runtime configuration from Dokploy’s environment injection or Infisical; it does not load .env files in production.
Production uses Neon for PostgreSQL, Redis Cloud for Redis, Aiven for OpenSearch, Cloudflare R2 for object storage, and a self-hosted InstantDB deployment. Development uses Aiven for PostgreSQL and OpenSearch; these environments must not share connection details or credentials.
The deployment is blocked until the account, infrastructure, and DNS owners have completed the items marked Required. Never commit credentials to the repository or paste them into tickets or chat. Store them in the production Infisical project at /server.
Deployment gates
Complete these gates before deploying the application:- Production infrastructure exists and is reachable from the server host.
- Every required production secret is present in Infisical under the production environment and
/serverpath. - Payment, email, SMS, OAuth, notification, moderation, video, chat, and storage providers have production credentials and approved webhook/redirect URLs.
- The OpenSearch endpoint has been verified as Learnille infrastructure. Do not launch against an endpoint whose ownership is uncertain.
- The production domain, short-link domain, email DNS records, OAuth consent screen, and payment webhooks are configured.
- A database backup and rollback target have been confirmed.
Required accounts and infrastructure
The following table is the production hand-off list.Account action means an account or resource must be created or activated before a secret can be supplied. Server configuration lists the variable names currently validated by server/src/config.ts.
Production configuration
Infisical
Create or verify the production environment in the Infisical project. Store the server secrets at/server. The package scripts default to INFISICAL_ENV=prod and INFISICAL_PATH=/server; set these explicitly in deployment automation so a staging secret set cannot be selected accidentally.
The server validates configuration during startup. In addition to provider values above, production must supply the application, security, database, queue, and upload values below:
Optional values should be set only when the corresponding feature is enabled. Do not add a provider secret merely because it appears in a local environment.
The startup key verifier also requires
KEY_PRIV and KEY_PUB in every non-docs environment. In production it additionally requires both CA_PEM and HTTP_CA_CRT, and checks that the PEM values have the expected format. These are not currently represented in the Zod configuration table, but a missing value stops application startup.
Domain and DNS checklist
- Point the API hostname to the production edge or reverse proxy.
- Configure the frontend origin in
FRONTEND_URLandCORS_ORIGIN. - Configure the short-link hostname in Shlink and
SHLINK_BASE_URL. - Add the SMTP provider’s SPF and DKIM records. Add DMARC according to the email policy.
- Register the exact Google OAuth origins and callback URLs.
- Register Paystack and Flutterwave webhook URLs over HTTPS:
/payment/paystack-webhookand/payment/flutterwave-webhook. Do not use the older/payment/webhook/:gatewayroute for new configuration. - Configure Bunny Stream, OneSignal, Soketi, and R2 allowed origins as applicable.
- Confirm certificates renew automatically.
Provisioning order
Provision dependencies in this order so the API is not started against partial state:- Dokploy application, VPS networking, reverse proxy, TLS, health check, and log retention.
- Neon database, Redis, Cloudflare R2 buckets, self-hosted InstantDB, Soketi, Shlink, and OpenSearch connectivity.
- Provider accounts, DNS records, OAuth configuration, webhook endpoints, and production credentials.
- Infisical production variables and a controlled secret-access policy.
- Confirm the database schema strategy. The repository currently contains TypeORM migration files, including an initial schema migration; an empty production database must not be created through synchronization.
- Application build and production artifact verification.
- Database migrations, database seed data, InstantDB schema and permissions, and Novu workflow synchronization.
- Process start.
- OpenSearch index creation and content reindexing.
- Smoke tests and monitoring checks.
Deploy the server with Dokploy
Configure a Dokploy application from the independentserver repository and use the repository’s Dockerfile. The Dockerfile builds the application with pnpm run build:raw and starts it with pnpm run start:prod by default. The container exposes port 3000; set the Dokploy service port to the value used by the deployed application and route the production API hostname to it.
Inject the production variables through Dokploy. If the container is expected to fetch secrets from Infisical at runtime instead, configure the required Infisical authentication variables in Dokploy and keep INFISICAL_ENV=prod and INFISICAL_PATH=/server explicit. Use one secret-injection model consistently; do not inject partial values through Dokploy while expecting the container to complete the set from Infisical.
Run database and initialization commands as Dokploy pre-deploy/release commands or as controlled one-off jobs using the same image and production environment. The commands below do not print or copy secret values.
build:raw. Do not run a second application build as part of the release command.
If the production database is empty, stop here unless the complete schema baseline has been reviewed and is represented by migrations. Do not use TypeORM synchronization to create or repair the production schema.
The current src/seeder.ts passes synchronize: true to DatabaseModule.register(). Because that explicit option is spread over the module’s default, pnpm run seed:prod is not safe to run against production until the seeder is changed to use synchronize: false and the change is reviewed. This is a code-level deployment blocker, not a documentation choice.
After migrations are successful, seed only idempotent reference data with the corrected production-safe seeder, then push external schemas and workflows. If the Dokploy environment only injects variables directly, invoke the underlying commands without an extra infisical run wrapper. If the image is configured to authenticate to Infisical, use the package scripts that wrap Infisical instead.
sync:novu, verify that NOVU_PATH is the production bridge URL and that the command receives the production NOVU_SECRET_KEY from Infisical. Do not run a development or staging bridge against the production project.
The current instant:schema:prod and instant:perms:prod package scripts load env/.env.production with dotenv; they do not load Infisical themselves. They must either be changed to consume the Infisical-injected environment or replaced with an equivalent production command before launch. This is especially important for self-hosted InstantDB because the CLI target must be explicit and must not silently default to a hosted service. Do not create a production .env file as a workaround.
After that script/tooling correction, run both pnpm run instant:schema:prod and pnpm run instant:perms:prod against the self-hosted production app, then verify that the server can create and read a room before starting public traffic.
Dokploy’s runtime command should be pnpm run start:prod, or the equivalent node -r ./path-alias-register.js dist/main.js with NODE_ENV=production and the complete production environment already injected.
Do not use TypeORM synchronization in production. The database module disables synchronization for production; schema changes must be applied through reviewed migrations.
Start the Dokploy service
Start the process only after the database migration, corrected seed, InstantDB schema and permissions, and Novu workflow synchronization have completed: Dokploy starts the container with the Dockerfile’s defaultpnpm run start:prod command after the release commands succeed. Do not use PM2 in production; Dokploy owns the container lifecycle, restarts, deployment history, and logs.
The server bootstrap creates the local ./uploads directory automatically, validates configuration, initializes the database, Redis, queues, mailer, Novu, OpenSearch, and InstantDB clients, writes openapi.json, and listens on PORT. Those clients still require their remote services and credentials to be reachable before startup.
Initialization that still requires the running server
OpenSearch has no production bootstrap script in the currentserver/package.json. After the server is healthy, create or verify the Learnille indexes and run the approved product/content reindex operation through the server’s OpenSearch administration flow or a separately documented worker command. Do not invent a shell command for this step until the production index owner and index prefix are confirmed.
The server also needs post-start verification for health indicators, queue processors, payment webhooks, notification delivery, storage, and websocket connectivity. These are runtime checks, not schema initialization.
Verification checklist
Check each item against production after deployment:- API health endpoint reports the database, Redis, and OpenSearch dependencies as healthy.
- Swagger is reachable only at the intended API domain and is protected according to the production access policy.
- A new user can complete Google OAuth or password registration and receive an email or SMS OTP.
- Paystack and Flutterwave test transactions reach the correct production webhook handlers, and the webhook routes answer
200. - A calendar feed created with
POST /calendar/feedopens in Google Calendar and shows an upcoming session. - A verification email, password-reset email, Novu notification, and OneSignal notification are delivered.
- R2 public upload, private upload, signed download, and multipart upload work.
- Bunny Stream upload and playback work.
- Bunny Stream has an active prepaid credit balance; do not rely on the short free trial for production traffic.
- GetStream chat and self-hosted InstantDB room creation work.
- Currency conversion succeeds through the selected provider and fails safely when that provider is unavailable.
- Moderation requests work, provider failures are observable, and OpenRouter/Perspective spend alerts are active.
- Soketi websocket connections work through the production edge.
- Shlink creates and resolves a short link.
- OpenSearch indexing and search work against the verified Learnille host.
- Logs, error tracking, queue failures, database backups, and uptime alerts are visible to the on-call team.
Rollback
- Roll back the Dokploy deployment or route traffic to the previous healthy container.
- Keep the database intact unless the release includes an approved rollback migration.
- Re-deploy the previous known-good image through Dokploy.
- Confirm health, queue processing, authentication, payments, and uploads.
- Record the failed release, affected migrations, provider errors, and the follow-up action.