App repository for my VPS.
This is both:
- A real setup I run and iterate on.
- A portfolio-friendly repo that shows end-to-end infra and ops (Terraform, Ansible, Docker).
Current apps:
- Traefik (reverse proxy + TLS)
- Uptime Kuma (monitoring)
- LibreChat
traefik/(Traefik config/compose; work in progress)uptime-kuma/compose.yamllibrechat/compose.yaml
Planned:
terraform/(Google Cloud: VM, firewall, static IP, optional DNS)ansible/(bootstrap: Docker/Compose + basic hardening)
- Provision a VPS on Google Cloud (planned via Terraform) and note its public IP.
- Ensure inbound
80/tcpand443/tcpare allowed (and22/tcpfor SSH, ideally restricted). - Install Docker + Docker Compose on the VPS (manual for now; planned via Ansible).
- Point your DNS
A/AAAArecords to the VPS public IP. - Update the domain(s) in the Traefik config (host rules / ACME settings).
- Start Traefik: run
docker compose up -dfromtraefik/. - Start Uptime Kuma:
docker compose -f uptime-kuma/compose.yaml up -d. - Start LibreChat:
docker compose -f librechat/compose.yaml up -d.
librechat/compose.yamlcurrently binds80/443directly and will conflict with Traefik as-is; for a Traefik-fronted deploy, remove those bindings and route it through Traefik.- Don’t commit secrets. Use a local
.env(not checked in) or a secret manager.
Install the gcloud tool https://docs.cloud.google.com/sdk/docs/install-sdk
gcloud auth logingcloud projects list
gcloud config set project project-19f7eed5-b04a-472c-b23
gcloud compute instances list
gcloud compute ssh <instace> gcloud compute zones list
gcloud compute machine-types listgcloud compute instances create my-vps \
--zone=europe-southwest1-a \
--machine-type=e2-medium \
--boot-disk-size=30GB \
--boot-disk-type=pd-balanced \
--image-family=ubuntu-2404-lts-amd64 \
--image-project=ubuntu-os-cloudgcloud compute firewall-rules create https --allow tcp:443
gcloud compute firewall-rules create http --allow tcp:80Start, stop and delete.
# stop and resume gcloud compute instances stop my-vps gcloud compute instances start my-vps # DELETE gcloud compute instances delete my-vps
Strato - gc.rudolphmaier.de
https://gc.rudolphmaier.de https://gpt.rudolphmaier.de https://speed.rudolphmaier.de https://up.rudolphmaier.de
ansible-playbook -i ansible/inventory.ini ansible/playbooks/docker-ubuntu.yml# currently using rsync, later clone a github repo
rsync -av --exclude='.git' ./ gc.rudolphmaier.de:~/ssh gc.rudolphmaier.de
docker network create web
docker compose -f apps/traefik/compose.yaml up -d
docker compose -f apps/librespeed/compose.yaml up -d
docker compose -f apps/uptime-kuma/compose.yaml up -d
docker compose -f apps/librechat/compose.yaml up -d./apps/
├── librechat
│ ├── dump # mongo db dump
│ ├── images
│ └── uploads
└── uptime-kuma
└── data # sqlite and datassh-keygen -t ed25519 -C "borgbase@ribeiromaier.de" -f ~/.ssh/id_ed25519_borgbase
# the private key is on my host inside keepassxc and can be consumed via the ubuntu ssh agent.
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoxXfyfp7ISJRDQfEMCs5/Y9lc8XSkn0GJTaxY467n9 borgbase@ribeiromaier.deRepo: ssh://k5ba1xct@k5ba1xct.repo.borgbase.com/./repo
ssh gc.rudolphmaier.de
mkdir -p ~/.ssh
chmod 700 ~/.ssh
umask 077
# copy the private key to the clipboard
cat > ~/.ssh/borgbase_ed25519 # ctrl - c
chmod 600 ~/.ssh/borgbase_ed25519

ssh-keyscan -H k5ba1xct.repo.borgbase.com >> ~/.ssh/known_hostsInitialize the repo (you will be prompted for a Borg passphrase):
export BORG_REPO='ssh://k5ba1xct@k5ba1xct.repo.borgbase.com/./repo'
borg init --encryption=repokey-blake2 "$BORG_REPO"Export the repo key OFF the VPS (required for disaster recovery):
borg key export "$BORG_REPO" /tmp/borgbase-repo-key.txtsaved to keepassxc
from old server
docker exec chat-mongodb sh -lc 'mongodump --db LibreChat --archive --gzip' \
> librechat/dump/mongodb-LibreChat.archive.gz#!/usr/bin/env bash
export BORG_REPO='ssh://k5ba1xct@k5ba1xct.repo.borgbase.com/./repo'
# Backup Librechat
mkdir -p librechat/dump
docker exec chat-mongodb sh -lc 'mongodump --db LibreChat --archive --gzip' \
> librechat/dump/mongodb-LibreChat.archive.gz
# docker exec vectordb sh -lc 'PGPASSWORD=mypassword pg_dump -U myuser -d mydatabase | gzip -c' > "/home/.../vps/librechat/dump/postgres-mydatabase-$(date +%F_%H%M).sql.gz"
docker compose -f librechat/compose.yaml down
docker compose -f uptime-kuma/compose.yaml down
borg create --stats --compression zstd,6 \
"$BORG_REPO::vps-$(date +%F_%H%M)" \
librechat/dump \
librechat/images \
librechat/uploads \
uptime-kuma/data
borg prune --stats --keep-daily 2 --keep-weekly 1 --keep-monthly 1
# Bring services back:
docker compose -f uptime-kuma/compose.yaml up -d
docker compose -f librechat/compose.yaml up -dexport BORG_RSH='ssh -i ~/.ssh/borgbase_ed25519 -o IdentitiesOnly=yes'
export BORG_REPO='ssh://k5ba1xct@k5ba1xct.repo.borgbase.com/./repo'
borg info 'ssh://k5ba1xct@k5ba1xct.repo.borgbase.com/./repo'
# get latest archive name
LATEST="$(borg list --short --last 1 "$BORG_REPO")"
echo "$LATEST"
# Restore that archive (run from your .../vps repo root):
docker compose -f librechat/compose.yaml down
docker compose -f uptime-kuma/compose.yaml down
borg extract "$BORG_REPO::$LATEST" \
librechat/dump \
librechat/images \
librechat/uploads \
uptime-kuma/data
# Bring services back:
docker compose -f uptime-kuma/compose.yaml up -d
docker compose -f librechat/compose.yaml up -d
# Then restore DBs from the extracted dumps:
gunzip -c librechat/dump/mongodb-LibreChat.archive.gz | \
docker exec -i chat-mongodb sh -lc 'mongorestore --drop --archive'ansible-playbook -i ansible/inventory.ini ansible/playbooks/borg-restore.yml