A micro-orchestra designed to deploy the Ramper website on a Proxmox Docker LXC. It features internal traffic management via Caddy, monitoring via Uptime Kuma, and automated updates via Watchtower (for the web app) + Renovate (for infrastructure images).
- Web Application: Astro-based site (
ghcr.io/alpargatagazer/ramper-web) with hybrid API routing for subscriptions and automated newsletter delivery. - Proxy: Caddy acting as a multi-port internal reverse proxy.
- Monitoring: Uptime Kuma to track service health.
- Logs: Dozzle for a web-based view of container logs.
- Newsletter: Listmonk self-hosted email subscription manager.
- Database: PostgreSQL dedicated backend database for Listmonk.
- Auto-Updates (Web App): Watchtower monitors only the
ramper-webcontainer and restarts it when a new image is published to GHCR. - Auto-Updates (Infrastructure): Renovate opens weekly PRs bumping pinned versions in
.env.images. Merge the PR → git-sync timer picks it up and redeploys.
Push to main → GitHub Actions CI → Build + Test → Push :latest to GHCR
↓
Watchtower detects new digest (every 5min)
↓
Restarts ramper-web container
↓
Container starts → newsletter:send runs
(sends email only if new post found)
Renovate PR (weekly) → You review + merge
↓
git-sync timer (every 5min) detects new commit
↓
git pull → docker compose up (with new image tags)
To pull the private/public web image, the host (LXC) must be authenticated. You will need a Personal Access Token (PAT) with read:packages permissions.
Run this command on your LXC:
echo "YOUR_PAT_TOKEN" | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdinNote
Watchtower reads Docker's auth config (/root/.docker/config.json) to pull authenticated images from GHCR.
The orchestra exposes distinct ports to the host:
- Port 8123: Ramper Web.
- Port 8124: Uptime Kuma.
- Port 8125: Dozzle (Logs).
- Port 9000: Listmonk (newsletter admin).
Configure your Cloudflare Tunnel (in its own LXC) to point to these specific ports based on your desired subdomains/paths.
- Clone this repository into your LXC.
- Copy
.env.exampleto.envand fill in values. Note that PUBLIC_NEWSLETTER_LIST_ID and PUBLIC_NEWSLETTER_LIST_UUID are taken from Listmonk admin UI so you need to init that first. .env.imagesis already committed and managed by Renovate — no need to copy it.- Deploy the stack:
./orchestra.sh upUse the orchestra.sh script for common operations:
./orchestra.sh up: Pull latest images, start the entire stack, and cleanup../orchestra.sh down: Stop and remove containers../orchestra.sh status: View container health../orchestra.sh logs: View live logs of the services../orchestra.sh prune: Remove dangling images to free disk space.
Watchtower runs as a service in the compose stack and polls GHCR every 5 minutes. It only updates containers that carry the com.centurylinklabs.watchtower.enable=true label — currently only ramper-web.
You can optionally configure Watchtower notifications by setting WATCHTOWER_NOTIFICATION_URL in .env (supports Slack, Discord, Telegram, email via shoutrrr).
Image versions are pinned in .env.images with Renovate annotations. Renovate opens PRs automatically when upstream images have new versions. Once merged, the git-sync timer picks up the change within 5 minutes and redeploys.
To enable the git-sync timer on the LXC:
./orchestra.sh setup-git-syncThis deployment implements Docker Compose Secrets to avoid storing passwords in environment variables:
- Admin credentials and database passwords are stored inside the
./secrets/directory on the host. - These files are automatically generated with secure random strings upon running
./orchestra.sh upfor the first time../secrets/listmonk_db_password.txt: Database access password../secrets/listmonk_admin_username.txt: Listmonk Superadmin user../secrets/listmonk_admin_password.txt: Listmonk Superadmin password../secrets/listmonk_api_username.txt: API user for the web container (defaults toapiuser). This must be created from the Listmonk web app UI after initial setup../secrets/listmonk_api_password.txt: Auto-generated password for the API user. This must be created from the Listmonk web app UI after initial setup.
Important
Because Listmonk only auto-creates the Superadmin user on installation, the auto-generated API user will not exist in the database.
After the first deployment, you must log into the Listmonk dashboard using the admin credentials, navigate to Settings → Users, and manually create the apiuser. It will give you a password and you will need to set it in ./secrets/listmonk_api_password.txt. The web container relies on this API user to send automated newsletters.
Persistent data is stored in the ./volumes directory:
caddy_data/: Caddy certificates and config.uptime-kuma/: Database and settings for the monitoring service.listmonk_db/: PostgreSQL database files.web_data/: State file (last-newsletter.json) for newsletter tracking.