What's my server setup?

Running six websites on a 5€ server
Featured image

I’ve had a VPS (virtual private server) for 2019-04-30 years. Initially, I bought it to run the game server for my mobile multiplayer game Four in a Row. As time went on, I added other services and learned a lot about DevOps.

At some point I migrated from my old hosting provider to Hetzner because of ongoing connectivity issues. Same price, faster drive, and no more problems. During the migration, I took the opportunity to dockerize all services to simplify my setup.

# My DevOps Setup

DevOps setup running on my VPS

This is a rough overview of the current state of my VPS. From left to right …

  1. I use Warp as a terminal.
    It allows me to write shell commands like convert all .mov files to .mp4 in plain english.
  2. As a shell I use Zsh with Oh My Zsh for autocompletion and other niceties;
    Tmux to manage many terminal sessions;
    and Mosh for roaming and easy reconnection for when I’m working on the go.
  3. All my services are running inside Docker with Docker Compose.
    Check out the detailed example below.
  4. Traefik listens on port 443 and 80 to route web traffic to the correct services.
    It’s configured dynamically using Docker labels in the service’s docker compose file. This way the config stays closely coupled with the service.
  5. All requests are proxied by Cloudflare, which is free. I also use Cloudflare to buy my domains and manage my DNS settings, and it provides features like caching and DDOS protection.

# Simple Example: this website

Diagram showing an overview of the docker setup for this website.
Check out the source for this configuration: docker-compose.yaml.

Let’s start on the right this time. Incoming HTTP requests pass through Cloudflare and Traefik. If they are intended for filippo-orru.com, they are handled by nginx, which serves static files (HTML, CSS and JS) from a volume.

This website uses a somewhat unconventional self-made CD (continuous delivery) setup. As you can see in the diagram, when a commit is pushed to master, GitHub sends a webhook to the webhook-listener service. This triggers a deploy script that pulls the newest code from GitHub and builds it using Hugo. Lastly, the output files are copied to the same volume that nginx uses.

If I were to rebuild the CD system today, I would likely choose something like GitHub Actions or a self-hosted alternative. This would offer multiple benefits like a history of builds, e-mail notifications on errors, and a more stable system.

Screenshot of Plausible analytics showing visit statistics for the past 30 days.
Screenshot of Plausible analytics

On a separate subdomain, I run Plausible, an easy to use and privacy-friendly Google Analytics alternative. As mentioned above, both services tell traefik what host and port they serve via docker labels.

# Complex Example

Check out this complex example of a docker compose configuration. It’s used by Kontento, a web app I’m developing.

ALT
  1. the web app is written in Flutter and served by nginx from an external docker volume.
    To deploy, the app is built, then static HTML/CSS/JS files are pushed to a dedicated deploy repository, and the changes are pulled to the docker volume.
  2. the API handles requests, reads from and writes to the database, and executes recurring tasks.
  3. the MongoDB database stores all necessary data in an external volume.
  4. Grafana is used to visualize some stats like retention, daily active users, and app starts. It connects to mongodb for data.
  5. to send emails for password resets, I use Protonmail Bridge. It’s intended for desktop mail clients and enables sending emails via SMTP to Proton Mail. This community-run docker image simplifies deployment.
  6. the database-backup service does what the name suggests. Every night, it creates a compressed backup using mongodump, which is uploaded to Backblaze B2, a cheap AWS S3 alternative, using s3cmd. The script also takes care of deleting old backup files.

Additionally, just like above, the services that receive web traffic configure their routing rules via docker labels. In this case, the web app runs on [domain].com, the api on api.[domain].com, and grafana on stats.[domain].com.

# More posts


Hi, I’m Filippo. A software engineer living in Graz, Austria. On this blog I write about my programming projects and other interests. Learn more about me.