Deployment
Overview for deploying LaunchKit Pro to a VPS or cloud VM. Refer to launchkit-pro-infra samples for Nginx, systemd, and scripts — never copy private credentials from any internal runbook into buyer docs.
Architecture overview
Internet → Nginx (443) → app.yourdomain.com → Next.js :3000
→ api.yourdomain.com → NestJS :4000
→ docs.yourdomain.com → static VitePress buildAPI and frontend bind to localhost; Nginx is the public edge.
Production VPS layout
LaunchKit Labs reference paths (adjust for your domain):
| Path | Purpose |
|---|---|
/var/www/launchkitlabs/apps/api/current | Active API release (symlink) |
/var/www/launchkitlabs/shared/backend/.env | Shared API secrets |
/var/www/launchkitlabs/logs/api-out.log | API stdout log |
/var/www/launchkitlabs/deploy-backend.sh | Production deploy entrypoint |
Full layout: VPS Deployment.
Domains: app.launchkitlabs.com, api.launchkitlabs.com, docs.launchkitlabs.com, launchkitlabs.com.
Backend deployment (robust script)
Use the production script (copy from launchkit-pro-infra/scripts/deploy-backend.sh):
/var/www/launchkitlabs/deploy-backend.shFlow
- Clone new release to
apps/api/releases/<timestamp>/ - Symlink shared env — never
source .env(spaces in values break shell) npm ci --include=dev— required fornest buildnpx prisma generate+npx prisma migrate deploynpm run buildnpm prune --omit=dev— after successful build only- Preflight — start on port
4001, health check before promoting - Switch
currentsymlink →systemctl restart launchkit-api - Production health — retry up to 60 seconds
- Rollback previous release only if health still fails
WARNING
Do not run npm ci --omit=dev before npm run build — causes nest: not found. See Deployment Gotchas.
systemd example:
WorkingDirectory=/var/www/launchkitlabs/apps/api/currentExecStart=.../current/dist/main.jsEnvironmentFile=/var/www/launchkitlabs/shared/backend/.env
Health check:
curl -sf http://127.0.0.1:4000/health
curl -sf https://api.launchkitlabs.com/healthFrontend deployment
- Clone
launchkit-pro-frontend - Set production
.env.localor build-time env:
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
NEXT_PUBLIC_APP_NAME=LaunchKit Pro
NEXT_PUBLIC_DOCS_URL=https://docs.yourdomain.com
NEXT_PUBLIC_SEARCH_SHORTCUT_ENABLED=trueSearch uses the backend GET /search API. Docs links in search results use NEXT_PUBLIC_DOCS_URL. See Dashboard Search.
Security: set TRUST_PROXY=true behind Nginx, keep RATE_LIMIT_ENABLED=true, and list production origins in CORS_ALLOWED_ORIGINS. See Security Hardening.
npm ci && npm run build && npm run start
Example systemd: launchkit-frontend.service → port 3000
Docs site deployment
Build static buyer docs:
cd launchkit-pro-docs
npm ci
npm run buildOutput: .vitepress/dist — serve as static files at docs.yourdomain.com.
root /var/www/yourapp/sites/docs;
location / {
# VitePress emits guide/page.html for clean URLs like /guide/updates
try_files $uri $uri.html $uri/ =404;
}
error_page 404 /404.html;Nginx routing
| Host | Upstream |
|---|---|
app.yourdomain.com | http://127.0.0.1:3000 |
api.yourdomain.com | http://127.0.0.1:4000 |
docs.yourdomain.com | static files |
Sample config structure in launchkit-pro-infra/samples/nginx/.
SSL
Use Let's Encrypt (Certbot):
sudo certbot --nginx -d yourdomain.com -d app.yourdomain.com \
-d api.yourdomain.com -d docs.yourdomain.comRenewal dry run:
sudo certbot renew --dry-runUpload size (Nginx)
On API server block:
client_max_body_size 10m;Match backend MAX_UPLOAD_SIZE_MB and frontend NEXT_PUBLIC_MAX_UPLOAD_SIZE_MB.
Uploads and avatars (production)
Set a persistent upload directory outside release folders:
STORAGE_PROVIDER=LOCAL
STORAGE_UPLOAD_DIR=/var/www/yourapp/shared/uploads
STORAGE_PUBLIC_BASE_URL=https://api.yourdomain.com/uploads- Avatars and workspace files are served at
https://api.yourdomain.com/uploads/... - Frontend uses
NEXT_PUBLIC_API_URLto resolve asset URLs - Include the upload directory in filesystem backups (not included in PostgreSQL dumps)
- Do not bundle runtime uploads in buyer ZIP packages
- For durable media storage in production, prefer S3, Cloudflare R2, or Cloudinary. Keep those provider secrets in backend env only.
- Provider migration from local disk to cloud storage is future work; back up local uploads until migrated.
Systemd services
Infra samples include:
launchkit-api.service.sample— backendlaunchkit-frontend.service.sample— frontend
Typical commands:
sudo systemctl daemon-reload
sudo systemctl enable launchkit-api launchkit-frontend
sudo systemctl start launchkit-api launchkit-frontend
sudo systemctl status launchkit-api --no-pagerDeployment scripts
Sample scripts in launchkit-pro-infra/samples/scripts/:
deploy-backend.sh.sampledeploy-frontend.sh.samplebackup-postgres.sh.sample
Customize paths and users for your server. Do not embed secrets in scripts committed to git.
Backups and restore
LaunchKit Pro includes a backup and restore foundation for self-hosted deployments. The app-level backup script is designed for VPS layouts such as:
/var/www/launchkitlabs/apps/api/current
/var/www/launchkitlabs/apps/frontend/current
/var/www/launchkitlabs/apps/docs/current
/var/www/launchkitlabs/shared/backend/.env
/var/www/launchkitlabs/shared/frontend/.env.productionManual run:
bash /var/www/launchkitlabs/infra/scripts/backup-launchkit.shDaily cron example:
0 2 * * * /var/www/launchkitlabs/infra/scripts/backup-launchkit.sh >> /var/www/launchkitlabs/logs/backup.log 2>&1Backups include PostgreSQL, uploads when present, shared env files, Nginx configs, deploy scripts, and release symlink metadata. The archive contains secrets if env files are included, so keep it private and never commit backups to Git.
Contabo/VPS automatic backups or snapshots are recommended for production. Local app-level backups are helpful for restores, but long-term production setups should also keep an offsite copy using S3/R2/Backblaze/Google Drive sync or rsync to another VPS.
See Backups & Restore.
Production environment notes
| Topic | Recommendation |
|---|---|
| Secrets | Inject via env manager; rotate JWT secrets periodically |
| Database | Dedicated PostgreSQL; regular backups |
| Backups | Enable VPS/provider backups, run the app-level backup script daily, and keep an offsite copy |
| Uploads | Persistent volume for LOCAL_UPLOAD_DIR; include it in backup/restore testing |
| API docs | API_DOCS_ENABLED=false if public explorer is undesired |
| Demo mode | Off unless running marketplace demo |
| CORS | FRONTEND_URL must match real app origin; add staging/preview hosts via CORS_ALLOWED_ORIGINS |
| AI provider | Default AI_PROVIDER=MOCK; add buyer-owned OPENAI_API_KEY or GEMINI_API_KEY only when enabling real AI |
| Billing provider | Default BILLING_PROVIDER=MOCK; add Stripe or Lemon Squeezy secrets to backend .env only when enabling live billing |
| Billing webhooks | When using Stripe/Lemon Squeezy, expose https://api.yourdomain.com/billing/webhooks/stripe and /billing/webhooks/lemon-squeezy over HTTPS |
| Outbound webhooks | Use HTTPS endpoint URLs; queues are optional, and direct delivery works when WEBHOOK_QUEUE_ENABLED=false |
| API keys | Set API_KEY_HASH_SECRET to a strong random value in production; never expose API keys in frontend env |
| Redis / queues | Optional; QUEUES_ENABLED=false by default — see Background Queues |
Docker deployment (optional)
VPS + systemd + Nginx remains the recommended production path. Docker is an optional alternative for buyers who prefer containers.
- Copy
docker-compose.example.ymltodocker-compose.yml - Fill env from
.env.example— never commit secrets - Validate:
docker compose -f docker-compose.example.yml config - Run:
docker compose up --build
See Docker for service details, build args, and production notes.