Retarget deployment from Plesk to Dokploy (Docker Compose)

This commit is contained in:
Leon Serfaty
2026-06-07 18:30:53 -04:00
parent f033f00379
commit 8138827657
16 changed files with 1001 additions and 170 deletions
+50 -92
View File
@@ -1,107 +1,65 @@
# Deploying PodcastYes on Plesk (Linux)
# Deploying PodcastYes on Dokploy
Single-VPS deployment: one Next.js web process + one worker process under PM2, an
**external** Postgres, **local-disk** storage, and **no Redis**.
Containerized deploy: one image, two services (`web` + `worker`) via Docker
Compose, a persistent volume for generated assets, an **external** Postgres, and
**no Redis**. Dokploy's bundled Traefik handles the domain, TLS, and SSE streaming.
## 0. Prerequisites on the VPS
Artifacts: [`Dockerfile`](../Dockerfile), [`docker-compose.yml`](../docker-compose.yml), [`.dockerignore`](../.dockerignore).
```bash
# Node 20+ (Plesk → Tools & Settings → Node.js, or nvm)
node -v # >= 20
## 1. Create the app in Dokploy
- New Project → **Create Service → Compose**.
- **Provider:** Git → `https://git.serfaty.co/admin/podcastyes`, branch `main`.
- **Compose path:** `docker-compose.yml`.
# ffmpeg (the worker shells out to it for audio stitching)
sudo apt update && sudo apt install -y ffmpeg
ffmpeg -version
# PM2 (run outside Plesk's Passenger/Node extension)
sudo npm install -g pm2
## 2. Environment (Dokploy → Environment tab)
These feed both the build args (`NEXT_PUBLIC_*`) and runtime env. Minimum:
```
## 1. Database
Create the database on your external Postgres host and put its URL in `.env` as
`DATABASE_URL`. pg-boss auto-creates its own `pgboss` schema on first run.
## 2. Code + env
```bash
cd /var/www/vhosts/<your-domain> # your vhost docroot
git clone <repo> app && cd app # or Plesk Git deployment
cp .env.example .env # then fill in real values
npm ci
DATABASE_URL=postgresql://user:pass@host:5432/db?schema=public
BETTER_AUTH_SECRET=<openssl rand -base64 32>
BETTER_AUTH_URL=https://your-domain
NEXT_PUBLIC_APP_URL=https://your-domain
OPENAI_API_KEY=...
ELEVENLABS_API_KEY=...
RESEND_API_KEY=... # optional (emails)
GOOGLE_CLIENT_ID=... # optional (OAuth)
GOOGLE_CLIENT_SECRET=...
# Billing (optional until you sell)
STRIPE_SECRET_KEY=...
STRIPE_WEBHOOK_SECRET=...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=...
STRIPE_PRICE_CREATOR_MONTHLY=... # + PRO/AGENCY, _YEARLY
PAYPAL_CLIENT_ID=...
PAYPAL_CLIENT_SECRET=...
PAYPAL_WEBHOOK_ID=...
PAYPAL_API_BASE=https://api-m.paypal.com
PAYPAL_PLAN_CREATOR=... # + PRO/AGENCY
```
`STORAGE_DIR` and `PORT` are set in the compose file — don't override.
Fill in `.env`: `DATABASE_URL`, `BETTER_AUTH_SECRET` (`openssl rand -base64 32`),
`BETTER_AUTH_URL` / `NEXT_PUBLIC_APP_URL` (your https domain), `OPENAI_API_KEY`,
`ELEVENLABS_API_KEY`, `STORAGE_DIR` (absolute, e.g.
`/var/www/vhosts/<your-domain>/storage`), Stripe + PayPal keys, `RESEND_API_KEY`.
## 3. Domain + SSL
Dokploy → the **web** service → **Domains** → add your domain, container port **3000**, enable HTTPS (Let's Encrypt). Traefik streams responses, so the episode-generation SSE works with no extra config.
## 3. Migrate, seed, build
## 4. Deploy
Hit **Deploy**. The build installs deps + ffmpeg, runs `next build`, and starts:
- `web` runs `prisma migrate deploy` (applies all migrations) then `next start`.
- `worker` runs the pg-boss consumer (ffmpeg available in-image).
Both share the `storage` volume at `/app/storage` (mp3/art/exports). Add a volume backup schedule in Dokploy.
## 5. First admin
Dokploy → web service → **Terminal** (or `docker exec`):
```bash
npx prisma migrate deploy # create all tables
npm run db:seed # populate the Plan catalog
npm run build # next build (standalone) + postbuild asset copy
mkdir -p "$STORAGE_DIR"/{mp3,art,exports}
npx tsx scripts/make-admin.ts you@email.com
```
## 4. Run under PM2
```bash
pm2 start ecosystem.config.js # starts podcastyes-web + podcastyes-worker
pm2 save # remember the process list
pm2 startup # print the systemd command to run once (resurrect on reboot)
pm2 logs # tail logs
```
The web process listens on `127.0.0.1:3000`; the worker consumes the pg-boss queue.
## 5. nginx (reverse proxy + public art)
Plesk → Domains → your domain → **Apache & nginx Settings** → *Additional nginx
directives*. Paste `deploy/nginx-podcastyes.conf` (replace `PODCASTYES_DOMAIN` and
the storage path). `proxy_buffering off` is required for the live progress stream (SSE).
## 6. SSL
Plesk → SSL/TLS Certificates → install **Let's Encrypt** for the domain. Make sure
`BETTER_AUTH_URL` / `NEXT_PUBLIC_APP_URL` use `https://`.
## 7. Webhooks
- **Stripe**: Dashboard → Developers → Webhooks → add endpoint
`https://<domain>/api/webhooks/stripe`, events `checkout.session.completed`,
`customer.subscription.created/updated/deleted`. Put the signing secret in
`STRIPE_WEBHOOK_SECRET`. Create the 3 paid Prices and set the `STRIPE_PRICE_*` env vars.
- **PayPal**: create a subscription Product + Plans (Creator/Pro/Agency), set
`PAYPAL_PLAN_*`. Add a webhook to `https://<domain>/api/webhooks/paypal` for the
`BILLING.SUBSCRIPTION.*` events and set `PAYPAL_WEBHOOK_ID`.
## 8. First admin
```bash
# After signing up in the app once:
npm run make-admin you@email.com
```
Then visit `/admin`.
## 9. Redeploys (zero-downtime)
## 6. Webhooks
- Stripe → `https://your-domain/api/webhooks/stripe` (events `checkout.session.completed`, `customer.subscription.created/updated/deleted`); set `STRIPE_WEBHOOK_SECRET` + the `STRIPE_PRICE_*`.
- PayPal → `https://your-domain/api/webhooks/paypal` (`BILLING.SUBSCRIPTION.*`); set `PAYPAL_WEBHOOK_ID` + `PAYPAL_PLAN_*`.
```bash
git pull
npm ci
npx prisma migrate deploy
npm run build
pm2 reload ecosystem.config.js
```
## Redeploys
Push to `main` → Dokploy auto-deploys (enable the Git webhook), or click **Redeploy**. Migrations run automatically on each `web` boot.
## Backups
`STORAGE_DIR` (generated MP3s + art) is the only state not in Postgres — add it to a
nightly offsite backup. The DB is your external Postgres provider's responsibility.
## Scaling later (optional)
- Move the worker to a second VPS pointing at the same `DATABASE_URL` — change nothing in code.
- Swap local disk for S3/R2 by adding `lib/storage/s3.ts` and switching the registry in `lib/storage/index.ts`.
## Notes
- **Assets** are served by the app (authed `/api/assets/...`, public `/api/public/episodes/[shareId]/...`) — no separate static route needed.
- **Scaling:** the `worker` can be scaled or moved to its own Compose/host pointing at the same `DATABASE_URL`; swap local disk for S3/R2 by adding `lib/storage/s3.ts` and switching the registry in `lib/storage/index.ts`.
- **Build args:** `NEXT_PUBLIC_*` are baked at image-build time; changing them requires a rebuild (Dokploy does this on deploy).
-31
View File
@@ -1,31 +0,0 @@
# Plesk → Domains → <your-domain> → Apache & nginx Settings →
# "Additional nginx directives". Paste the blocks below (adjust the storage path).
#
# Plesk already terminates SSL (Let's Encrypt) and proxies to Apache; these
# directives make nginx proxy directly to the Next.js standalone server instead,
# and serve public cover art straight from disk.
# Public cover art only (MP3s stay private, served via the authed /api/assets route).
location /media/art/ {
alias /var/www/vhosts/PODCASTYES_DOMAIN/storage/art/;
expires 7d;
add_header Cache-Control "public";
access_log off;
}
# Proxy everything else to the Next.js web process (PM2 podcastyes-web on :3000).
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Critical for Server-Sent Events (episode generation progress stream):
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
}