PinePods on Ubuntu 24.04 on Azure User Guide
Overview
PinePods is a complete, self-hosted podcast management and streaming server written in Rust. You run one server for your household or team, subscribe to podcasts, and your subscriptions, listen history, playback position, downloads and settings sync across every client — the built-in web player, desktop apps, mobile apps and a command line client. It has a full multi-user login system with an administrator account, per-user libraries, podcast search and discovery, episode downloads for offline listening, and a gpodder-compatible sync API so existing podcast apps can connect.
The cloudimg image runs the whole stack natively on a single VM: nginx on port 80 serves the web interface and reverse-proxies the API to the Rust pinepods-api server (port 8032) and the gpodder-compatible endpoints to the Go gpodder-api (port 8042). PostgreSQL is the datastore and a local Valkey/Redis cache backs it; both are bound to the loopback interface.
On the first boot of every deployed VM, a one-shot service generates a unique administrator password and a unique PostgreSQL password, resolves the VM's public IP and writes it into the server's advertised URL, runs the database migrations, seeds the administrator account and starts the stack. The per-VM secrets are written to /root/pinepods-credentials.txt with mode 0600. The shipped image carries an empty database with no seeded account — the administrator is created freshly on your VM's first boot with a password that exists nowhere else. Backed by 24/7 cloudimg support.
What is included:
- PinePods 0.9.0 (the official upstream runtime) — the Rust
pinepods-apiserver, the Gogpodder-api, and the compiled web interface - nginx reverse proxy on
:80in front of the API on:8032and the gpodder API on:8042 - PostgreSQL datastore (podcasts, episodes, users, history) and a Valkey/Redis cache, both bound to loopback
yt-dlpandffmpegfor episode downloads- Per-VM administrator password and PostgreSQL password generated at first boot, in a root-only file; the server URL resolved to the VM's public IP
- No seeded credential in the image — the admin account is created per-VM on first boot
pinepods-api.service,pinepods-gpodder.service,postgresql.service,redis-server.serviceandnginx.serviceas systemd units, enabled and active- 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point; scale up for more users or heavy downloading. NSG inbound: allow 22/tcp from your management network and 80/tcp (plus 443/tcp once you enable HTTPS) from the networks your listeners will reach PinePods on. Leave 8032 and 8042 closed — they are internal services reached only through nginx.
Step 1 — Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for PinePods by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create → Create.
Step 2 — Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name pinepods \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name pinepods --port 80 --priority 1010
Step 3 — Connect to your VM
ssh azureuser@<vm-public-ip>
First-boot initialisation completes in under a minute; the credentials file is in place and the site is serving by the time SSH is ready.
Step 4 — Confirm the services are running
systemctl is-active postgresql redis-server pinepods-api pinepods-gpodder nginx
curl -fsS http://127.0.0.1/api/health
All five services report active, and the health endpoint returns {"status":"healthy","database":true,"redis":true,...} — proof the full stack (nginx, the Rust API, the gpodder API, PostgreSQL and Valkey/Redis) is serving. nginx on :80 is the single public entry point; the API listens on :8032 and the gpodder API on :8042 behind it, with PostgreSQL and Valkey/Redis on loopback:

The five systemd units active, the nginx/API/gpodder listeners, and PinePods' /api/health endpoint served through nginx.
The running version and the health check confirm which release you are on, and the per-VM credentials file is in place with root-only permissions:
cat /opt/pinepods/share/current_version
curl -s http://127.0.0.1/api/health
sudo ls -l /root/pinepods-credentials.txt

PinePods 0.9.0, the health endpoint reporting the database and Redis connections up, and the root-only (0600) per-VM credentials file.
Step 5 — Review the per-VM secrets
PinePods' administrator password and database password are generated uniquely on the first boot of your VM and written to a root-only file:
sudo cat /root/pinepods-credentials.txt
The file records the resolved site URL, the administrator username and password, and the local PostgreSQL login. You can confirm the secure-by-default posture end to end from the same session — the rotated database password authenticates to PostgreSQL, and the per-VM administrator authenticates against the API and is issued an API key:
DBPASS=$(sudo grep '^pinepods.db.pass=' /root/pinepods-credentials.txt | cut -d= -f2-)
PGPASSWORD="$DBPASS" psql -h 127.0.0.1 -U pinepods -d pinepods_database -tAc 'SELECT 1 AS db_auth_ok'
ADMU=$(sudo grep '^pinepods.admin.user=' /root/pinepods-credentials.txt | cut -d= -f2-)
ADMP=$(sudo grep '^pinepods.admin.pass=' /root/pinepods-credentials.txt | cut -d= -f2-)
curl -s -u "$ADMU:$ADMP" http://127.0.0.1/api/data/get_key | head -c 60; echo

The per-VM database password authenticates to PostgreSQL, and the per-VM administrator logs in through the API and is issued a session key — both secrets are unique to this VM and exist in no other deployment.
Step 6 — Log in to the web interface
Open a web browser and navigate to http://<vm-public-ip>/. PinePods presents its login screen — sign in with the administrator username and password from /root/pinepods-credentials.txt.

The PinePods login — sign in with the per-VM administrator account generated on first boot.
The first time you log in, PinePods walks you through a short Initial Setup to pick your language, time zone and date/time formats, then drops you on the home dashboard. From here Quick Links reach your Saved episodes, Downloads, History, Feed and Podcasts, "This Week" shows your listening stats, and Top Podcasts surfaces your library.

The home dashboard — quick links, weekly listening stats, and your top podcasts.
Step 7 — Subscribe to podcasts
Use the Search New Shows box in the top bar to find podcasts by name (backed by the Podcast Index), or add a feed by its RSS URL, then subscribe. Your subscriptions appear under Podcasts, each showing its artwork and episode count; open one to browse and play episodes, download them for offline listening, or queue them up.

The podcast library — subscribed shows with artwork and episode counts, searchable and filterable by category.
Because subscriptions, playback position and history live in PostgreSQL on your server, every client you sign in — the web player, the desktop and mobile apps, and gpodder-compatible apps via the sync API — stays in sync.
Step 8 — Manage users and settings
Sign in as the administrator and open Settings to reach both User Settings (themes, layout and playback preferences) and Admin Settings (create and manage user accounts, guest and self-service settings, the Podcast Index match, email and OIDC/SSO configuration, and server-wide options). PinePods is multi-user: add an account per person so everyone gets their own library while sharing one server.

The Settings page — User Settings and Admin Settings tabs for themes, user management, authentication and server options.
Step 9 — Inspect the library from the server (optional)
Everything a client shows is backed by PostgreSQL on the VM. You can inspect the library directly — for example, the subscribed podcasts and their episode counts:
DBPASS=$(sudo grep '^pinepods.db.pass=' /root/pinepods-credentials.txt | cut -d= -f2-)
PGPASSWORD="$DBPASS" psql -h 127.0.0.1 -U pinepods -d pinepods_database -P pager=off \
-c 'SELECT podcastname AS podcast, episodecount AS episodes FROM "Podcasts" ORDER BY podcastid'

The subscribed podcasts and episode counts read straight from PostgreSQL — proof the server owns your library.
Security baseline
The image ships fully patched at capture time and keeps patching itself: Ubuntu's unattended-upgrades service is enabled, so security updates are installed automatically on your running VM. The API, gpodder API, PostgreSQL and Valkey/Redis sit behind nginx, which is the single network-facing entry point — PostgreSQL and Valkey stay on loopback. Verify the state at any time:
systemctl is-enabled unattended-upgrades
grep -h . /etc/apt/apt.conf.d/20auto-upgrades
sudo ss -tlnp | grep -E '127.0.0.1:(5432|6379) '
Keep only 22, 80 and (once HTTPS is enabled) 443 open in the NSG; leave 8032 and 8042 closed so the API is reached only through nginx.
Enabling HTTPS
For any production deployment serve PinePods over HTTPS so logins and playback cannot be intercepted. The image ships with nginx, which certbot can configure automatically. The following assumes a DNS record pointing your fully qualified domain name at the VM's public IP address (and 443/tcp open in the NSG):
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d podcasts.your-domain.example \
--non-interactive --agree-tos -m you@your-domain.example \
--redirect
After certbot finishes, point the server's advertised URL at the HTTPS address so feed and asset links use it, then restart the service:
sudo sed -i \
-e 's|^SERVER_URL=.*|SERVER_URL=https://podcasts.your-domain.example|' \
-e 's|^HOSTNAME=.*|HOSTNAME=https://podcasts.your-domain.example|' \
/etc/pinepods/pinepods.env
sudo systemctl restart pinepods-api.service
Backup and maintenance
PinePods keeps all of its state — podcasts, episodes, users, history and settings — in PostgreSQL. Back it up regularly:
sudo -u postgres pg_dump pinepods_database > <backup-dir>/pinepods-db-$(date +%F).sql
Downloaded episode files live under /opt/pinepods/downloads; include that directory in your file backups if you keep offline downloads. Keep the OS patched with sudo apt update && sudo apt upgrade. See the PinePods documentation at https://www.pinepods.online/docs/Introduction for upgrades and advanced configuration.
Support
This image is backed by 24/7 cloudimg support. Guaranteed response within 24 hours, one hour average for critical issues. Contact support@cloudimg.co.uk for help with deployment, user and authentication (OIDC/SSO) configuration, TLS termination, gpodder sync, upgrades and database administration.
For general PinePods questions consult the documentation at https://www.pinepods.online/docs/Introduction. PinePods is licensed under the GNU GPL-3.0 and is a trademark of its respective owner; use here is nominative and does not imply affiliation or endorsement. All product and company names are trademarks or registered trademarks of their respective holders.