Skip to content

Self-hosting quickstart

A normal public self-hosted Pinchana deployment has two independently deployed applications:

  • Pinchana API — gateway, normal scraper modules, Gluetun, shared media cache, and optional DLP profile.
  • Pinchana Web — Next.js BFF/browser UI pointing at that API through server-only configuration.

You can run them on one host or separate hosts. In production, put both behind HTTPS reverse proxies.

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

Configure at least production VPN and machine authentication:

WIREGUARD_PRIVATE_KEY=REPLACE_WITH_PROVIDER_WIREGUARD_PRIVATE_KEY
GLUETUN_API_KEY=REPLACE_WITH_RANDOM_CONTROL_SECRET
PINCHANA_API_KEYS={"operator":"REPLACE_WITH_LONG_RANDOM_MACHINE_KEY"}

If Spotify should work, add its client ID/secret.

Resolve the current coherent API release to immutable image digests:

Terminal window
python scripts/update_rolling.py --env-file .env --dry-run
python scripts/update_rolling.py --env-file .env

Start and verify:

Terminal window
docker compose --env-file .env config --quiet
docker compose --env-file .env up --detach
curl --fail-with-body --silent --show-error http://127.0.0.1:8080/health

Expose only the API gateway through your public reverse proxy, for example https://api.example.com -> http://127.0.0.1:8080. Do not intentionally expose scraper or Gluetun control ports to the Internet.

Pinchana Web needs the API’s isolated browser-session flow. Create a Cloudflare Turnstile widget for the final Web hostname and set the API-side private values:

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

Recreate the gateway after changing environment:

Terminal window
docker compose --env-file .env up --detach --no-deps --force-recreate server

The public Turnstile site key is used by Web; the private secret stays on API.

For YouTube Shorts that require authenticated yt-dlp access, place Netscape-format cookie files under the directory configured by SHORTS_COOKIES_DIR (default ./secrets/yt-cookies).

For rootful Docker and the current root-running Shorts container:

Terminal window
sudo install -d -o root -g root -m 0700 secrets/yt-cookies
sudo chown root:root secrets/yt-cookies/cookies.txt
sudo chmod 0600 secrets/yt-cookies/cookies.txt

The directory is mounted read-only at /run/pinchana-cookies. With rootless Docker, own it with the host user running Docker instead. Never commit cookies.

In a separate checkout:

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

Set:

PINCHANA_API_URL=https://api.example.com
NEXT_PUBLIC_TURNSTILE_SITE_KEY=REPLACE_WITH_PUBLIC_SITE_KEY
SENTRY_MONITORING_ENABLED=false

PINCHANA_API_URL is server-only and must be HTTPS in production. The site key must be the public key corresponding to the API’s Turnstile secret and final Web hostname.

Validate/build/start:

Terminal window
docker compose --env-file .env config --quiet
docker compose --env-file .env up --detach --build
docker compose --env-file .env ps

By default Web binds to 127.0.0.1:3000. Reverse-proxy your public Web origin, for example https://pinchana.example.com -> http://127.0.0.1:3000.

A self-hosted Web deployment can point directly at its configured PINCHANA_API_URL without an instance certificate.

Configure PINCHANA_INSTANCE_PUBLIC_KEY only if you want that Web deployment to let users connect to additional project-certified custom API origins. The project signing private key must never be deployed to Web or API.

Ordinary YouTube/youtu.be downloads in Web require DLP protocol v2. Keep it disabled until the internal profile is deployed and canaried:

DLP_ENABLED=false

Pin DLP images with the same coherent release:

Terminal window
python scripts/update_rolling.py --env-file .env --dlp --dry-run
python scripts/update_rolling.py --env-file .env --dlp

Then follow DLP operations and security. Do not simply flip DLP_ENABLED=true before the preflight and canary succeed.

From outside the hosts, verify:

  • the API health endpoint is reachable over HTTPS;
  • the Web page loads over HTTPS;
  • Turnstile successfully establishes a browser session;
  • one normal supported URL processes and previews media;
  • protected media is not anonymously reusable from the API origin;
  • if DLP is enabled, /web/capabilities reports protocol 2 and a production YouTube smoke download succeeds.

Keep .env, cookies, VPN credentials, and all session/DLP secrets outside source control.