Server setup
Building a production backend server from an empty VPS.
Checklist-style flow matching the steps below.
Base system
An Ubuntu VPS with a non-root user that has sudo and SSH key login. Install Docker Engine with the Compose plugin and add the user to the docker group.
Recommended hardening: key-only SSH (no passwords, no root login) and a firewall allowing only 22, 80 and 443. Docker-published ports bypass ufw, which is one reason every internal service binds to 127.0.0.1 or publishes nothing.
Code
git clone https://github.com/Suman196pokhrel/vod-app.git ~/vod-app
cd ~/vod-app/infraConfiguration
cp .env.example prod.env
cp caddy/Caddyfile.prod.example caddy/Caddyfile.prod- Fill in every value in
prod.env(see Secrets). Generate strong random values; never reuse the example defaults. - Add
MAX_VIDEO_SIZEandMAX_THUMBNAIL_SIZE, which the template is missing. - In
Caddyfile.prod, set your API domain on the site line.
DNS
Point an A record for the API domain at the server. Caddy obtains the TLS certificate automatically on first start, so DNS must resolve before the stack starts.
Start the stack
alias dc='docker compose -f docker-compose.yml --env-file prod.env'
dc up -d --build
dc exec api alembic upgrade headStorage buckets
The backend creates the three buckets on startup. The processed and thumbnail buckets must allow anonymous reads so Caddy can serve HLS and images through /storage:
docker run --rm --network host --entrypoint sh minio/mc -c '
mc alias set local http://127.0.0.1:9000 <MINIO_ROOT_USER> <MINIO_ROOT_PASSWORD> &&
mc anonymous set download local/vod-processed &&
mc anonymous set download local/vod-thumbnails'All three commands run in one container: with separate docker run --rm calls, the alias set by the first is gone by the second. The raw-videos bucket stays private.
First admin
Sign up through the frontend, verify the email, then:
dc exec postgres psql -U vod_user -d vod_db \
-c "UPDATE users SET role = 'ADMIN' WHERE email = 'you@example.com';"Frontend
In the Vercel project, set NEXT_PUBLIC_API_URL=https://<api domain> and redeploy. Add the frontend's origin to CORS_ALLOW_ORIGINS in prod.env and restart the API.
To confirm on the current server
These are not recorded in the repository; check them on the live server and update this page:
| Item | Command |
|---|---|
| vCPUs, RAM, disk | nproc; free -h; df -h / |
| Firewall rules | sudo ufw status verbose |
| Docker log rotation | cat /etc/docker/daemon.json |
| SSH password login disabled | sudo sshd -T | grep -E 'passwordauthentication|permitrootlogin' |