VPS Deployment (Production Layout)
Reference for deploying LaunchKit Pro to a single VPS with Nginx, systemd, and release symlinks. Paths below match the LaunchKit Labs production layout; adjust BASE_DIR for your own domain.
No secrets in docs
Use placeholder values in documentation. Store real credentials only in /var/www/launchkitlabs/shared/backend/.env (or your secrets manager). Never source .env in shell — values may contain spaces. Use systemd EnvironmentFile= in production.
Domains
| Host | Purpose |
|---|---|
launchkitlabs.com | Marketing site |
app.launchkitlabs.com | Next.js frontend (dashboard + admin) |
api.launchkitlabs.com | NestJS API |
docs.launchkitlabs.com | VitePress buyer documentation (static) |
Directory layout
/var/www/launchkitlabs/
├── apps/
│ ├── api/
│ │ ├── current → symlink to active release
│ │ └── releases/
│ │ └── backend-YYYYMMDDhhmmss/ (or timestamp folders)
│ ├── frontend/
│ │ ├── current
│ │ └── releases/
│ └── docs/
│ ├── current
│ └── releases/
├── shared/
│ ├── backend/.env # shared API secrets (not in git)
│ └── frontend/.env.production
├── logs/
│ ├── api-out.log
│ └── api-error.log
├── backups/ # local backup archives
├── uploads/ # optional shared uploads root
├── infra/scripts/ # backup, restore samples
└── deploy-backend.sh # production backend deploy entrypointsystemd — API service
Example unit launchkit-api.service:
[Unit]
Description=LaunchKit Pro API
After=network.target postgresql.service
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/launchkitlabs/apps/api/current
EnvironmentFile=/var/www/launchkitlabs/shared/backend/.env
ExecStart=/usr/bin/node /var/www/launchkitlabs/apps/api/current/dist/main.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target- WorkingDirectory must point at
apps/api/current - ExecStart runs
dist/main.jsfrom the same release folder - Load env via EnvironmentFile, not shell
source
Backend deploy (robust flow)
Production script path:
/var/www/launchkitlabs/deploy-backend.shSource template: launchkit-pro-infra/scripts/deploy-backend.sh
Steps performed
- Clone new release into
apps/api/releases/<timestamp>/ - Symlink shared env →
.env(contents never printed) npm ci --include=dev— devDependencies required fornest buildnpx prisma generatenpx prisma migrate deploynpm run buildnpm prune --omit=dev— only after successful build- Preflight — boot new release on port
4100–4499(default4001), health check before promote - Switch
currentsymlink to new release systemctl restart launchkit-api- Production health — retry up to 60 seconds (
30 × 2s) - Rollback symlink + restart only if health still fails after retries
- Prune old releases (keeps last N)
Do not skip preflight
Promoting a broken build without preflight can take production offline. The script aborts before symlink switch if preflight fails.
Health endpoints:
curl -sf http://127.0.0.1:4000/health
curl -sf https://api.launchkitlabs.com/healthFrontend deploy
Typical flow (adjust to your deploy-frontend.sh if present):
- Clone/extract release to
apps/frontend/releases/<id>/ - Link
shared/frontend/.env.production npm ci --include=dev(Next.js build needs devDependencies)npm run buildnpm prune --omit=dev(optional)- Switch
apps/frontend/currentsymlink - Restart
launchkit-frontendsystemd service (port3000behind Nginx)
Required build-time public env:
NEXT_PUBLIC_API_URL=https://api.launchkitlabs.com
NEXT_PUBLIC_APP_NAME=LaunchKit Pro
NEXT_PUBLIC_DOCS_URL=https://docs.launchkitlabs.com
NEXT_PUBLIC_SUPPORT_EMAIL=support@launchkitlabs.comDocs deploy
- Build VitePress:
cd launchkit-pro-docs && npm ci && npm run build - Upload
.vitepress/distas tarball to server - Extract to
apps/docs/releases/<id>/ - Switch
apps/docs/currentsymlink nginx -t && systemctl reload nginx
Nginx
API and app proxy to localhost; docs served as static files. TLS via Let's Encrypt (certbot). See Deployment for sample server blocks.