Skip to content

Deploy the API

The canonical production deployment is a Linux VPS running Docker Engine and the repository’s docker-compose.yml with prebuilt GHCR images.

Create DNS for api.example.com, install current Docker Engine with the Compose plugin, clone recursively, and copy the example configuration:

Terminal window
git clone --recursive https://github.com/Pinchana/pinchana-api.git
cd pinchana-api
cp .env.example .env
chmod 600 .env

Ensure /dev/net/tun exists. Allocate disk capacity beyond CACHE_MAX_SIZE_GB, and allow the VPN’s required outbound traffic, DNS, HTTPS, and your administration channel.

At minimum, replace placeholders for the WireGuard private key, Gluetun API key, named machine API keys, Turnstile secret, expected Web hostname/action, and a random session secret of at least 32 characters. Add Spotify credentials if that module is enabled. If this origin will be selectable from the official Web app, install its issued public certificate envelope or mounted file.

WIREGUARD_PRIVATE_KEY=REPLACE_WITH_WIREGUARD_PRIVATE_KEY
GLUETUN_API_KEY=REPLACE_WITH_LONG_RANDOM_CONTROL_KEY
PINCHANA_API_KEYS={"production-client":"REPLACE_WITH_LONG_RANDOM_API_KEY"}
TURNSTILE_SECRET_KEY=REPLACE_WITH_PRIVATE_TURNSTILE_SECRET
TURNSTILE_EXPECTED_HOSTNAME=pinchana.example.com
TURNSTILE_EXPECTED_ACTION=turnstile-spin-v1
TURNSTILE_SESSION_SECRET=REPLACE_WITH_AT_LEAST_32_RANDOM_CHARACTERS
PINCHANA_INSTANCE_CERTIFICATE='REPLACE_WITH_ISSUED_ONE_LINE_JSON_ENVELOPE'
SPOTIFY_CLIENT_ID=REPLACE_IF_USED
SPOTIFY_CLIENT_SECRET=REPLACE_IF_USED

PINCHANA_INSTANCE_CERTIFICATE is required only when this origin should be selectable as a custom instance in the official Web app. A project maintainer issues it for the exact public API origin and the public Turnstile site key. Never copy the Ed25519 signing key to the server. See Custom API instances for issuance, file mounts, verification, and renewal.

Mount YouTube cookie files read-only through SHORTS_COOKIES_DIR only when required. Protect their directory as a credential store.

Private downloads require the separate dlp Compose profile. Keep DLP_ENABLED=false, generate distinct gateway/owner/Redis secrets, create /srv/pinchana-dlp/jobs with mode 0711, and pin the API, orchestrator, worker, and VPN images before starting it.

Terminal window
docker compose --profile dlp pull dlp-redis dlp-vpn dlp-api dlp-orchestrator
docker pull "$DLP_WORKER_IMAGE"
python scripts/dlp-prod-preflight.py --env-file .env --phase infra
docker compose --profile dlp up -d dlp-redis dlp-vpn dlp-api dlp-orchestrator

Require every DLP service to become healthy and complete anonymous plus encrypted-cookie canaries on a non-public instance. Then set DLP_ENABLED=true, recreate only server, run the enable preflight phase, and confirm authenticated /web/capabilities reports protocol 2. Roll back by setting the flag false and recreating server; active jobs can finish or expire independently.

See Private downloads (DLP) and the repository’s production rollout runbook.

Production scraper services and the gateway share Gluetun’s network namespace. The supplied Compose file publishes API, scraper, and control mappings on Gluetun for local operability; do not expose them all to the Internet.

  • Publish the gateway only through your HTTPS reverse proxy.
  • Bind API_HOST_PORT to a locally reachable port and firewall it from external clients if the proxy is on-host.
  • Deny public access to Gluetun control 8000 and scraper ports 8081–8089.
  • Expose only TCP 443 publicly (and 80 only for redirect/ACME if needed).
  • Keep the Docker socket and SSH/administration paths tightly restricted.

Configure Caddy, Nginx, Traefik, or another reverse proxy for https://api.example.com → the local gateway port. Preserve streaming and Range headers and choose timeouts longer than the gateway’s 120-second scrape forwarding timeout.

  1. Validate interpolation with docker compose config and inspect the output without sharing it—it contains secrets.
  2. Pull and start: docker compose pull && docker compose up -d.
  3. Inspect docker compose ps and docker compose logs --tail=200 gluetun server.
  4. Check curl --fail https://api.example.com/health.
  5. If an instance certificate is configured, check curl --fail https://api.example.com/web/identity and confirm it returns payload and signature.
  6. Send an authenticated /scrape, then fetch its media URL with the same X-API-Key and a test Range header.
  7. From the Web hostname, complete Turnstile and confirm /web/verify, scrape, and media playback through the Web BFF.

Pin explicit GHCR release tags for repeatability, then:

Terminal window
docker compose pull
docker compose up -d
docker compose ps
curl --fail https://api.example.com/health

Roll back by restoring previous tags and running docker compose up -d. Preserve scraper-cache. Never use docker restart gluetun; use docker compose up -d --force-recreate gluetun, recreate affected dependent services if necessary, and verify health.