adrian-altner.de/src/content/posts/en/2026/03/deploying-astro-ssr-to-vps-with-podman.md
Adrian Altner 4bf4eb03b1 Add new posts for Image Voice Memos, Initial VPS Setup on Debian, Local Webmention Avatars, Security Headers for Astro with Caddy, and Setting up Forgejo Actions Runner
- Created a new post on Image Voice Memos detailing a macOS app for browsing photos and recording voice memos with automatic transcription.
- Added a guide for Initial VPS Setup on Debian covering system updates, user creation, and SSH hardening.
- Introduced a post on caching webmention avatars locally at build time to enhance privacy and comply with CSP.
- Documented the implementation of security headers for an Astro site behind Caddy, focusing on GDPR compliance and CSP.
- Set up a Forgejo Actions runner for self-hosted CI/CD, detailing the installation and configuration process for automated deployments.
2026-04-22 23:00:10 +02:00

3.4 KiB

title description pubDate category tags seriesParent seriesOrder
Deploying Astro SSR to a VPS with Podman A practical VPS deployment flow for Astro SSR using Podman and podman-compose. 2026-03-02T11:24:00+01:00 en/on-premises-private-cloud
podman
deployment
astro-ssr-with-podman 2

With the Astro SSR container running cleanly on my laptop, I wanted to move it onto the VPS without inventing a new set of commands for production. The goal was a short, boring deploy sequence — the same compose file, the same entry point, just a different host.

This is the exact path that worked on a fresh Debian VPS.

The setup

  • VPS: Debian, freshly provisioned, SSH access as a non-root user.
  • Runtime: Podman and podman-compose from the distro packages.
  • Source of truth: a Git repository cloned into /opt/website.

Install runtime tools

sudo apt-get update
sudo apt-get -y install podman podman-compose git

Nothing exotic — the distro packages are modern enough for a single-container app.

Cloning the repository

Problem: My first git clone over HTTPS failed immediately with:

Invalid username or token. Password authentication is not supported

GitHub dropped password auth years ago, and the VPS had no credential helper configured. Rather than pasting a PAT onto the server, I switched to key-based SSH.

Implementation:

mkdir -p ~/.ssh && chmod 700 ~/.ssh
ssh-keygen -t ed25519 -C "vps-website" -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub

That public key went into the repository's Deploy Keys — read-only, scoped to this one repo, nothing else.

ssh-keyscan github.com >> ~/.ssh/known_hosts
git clone git@github.com:adrian-altner/website.git /opt/website

Solution: git pull now works unattended — no PAT expiry, no prompts, no shared account credentials on the box.

Starting the app

cd /opt/website
podman-compose -f compose.yml up --build -d

Three quick checks — the container is up, the logs show Astro booting, the local port answers:

podman ps
podman logs -f website
curl -I http://127.0.0.1:4321

A 200 from the last one means the app is healthy behind the scenes. It's still on localhost only — Caddy comes in a later post.

The gotchas I hit

Docker Hub auth errors on first build. The initial pull can fail with:

unable to retrieve auth token: invalid username/password

Even with public images. Pulling the base image explicitly once clears whatever stale state Podman is carrying:

podman pull docker.io/library/node:20-bookworm-slim

After that, podman-compose up --build -d goes through cleanly.

Compose provider warnings. If Podman announces it's falling back to an external docker-compose, installing and pinning podman-compose takes the ambiguity out:

sudo apt-get -y install podman-compose
export PODMAN_COMPOSE_PROVIDER=/usr/bin/podman-compose

From then on:

podman compose -f compose.yml up --build -d

What to take away

  • Key-based SSH with a Deploy Key beats HTTPS + PAT for unattended pulls — no expiry to manage.
  • The local compose.yml transfers to the VPS unchanged; that's the whole point.
  • A stale Podman auth cache is the most likely cause of mysterious first-pull failures — pull the base image directly once to reset it.
  • Pin PODMAN_COMPOSE_PROVIDER explicitly on servers so the command can't silently change behaviour under you.