Self-Hosting: The Basics

Welcome to volume 0.5 of my Self-Hosting series, going over a few core elements you’ll probably need for your server. This isn’t an end-all-be-all list of stuff, but odds are you’ll probably need most of these components eventually.

Additionally, the provided configs cover the basics. There’s plenty of additions you could make, like piping stats and logs into a monitoring dashboard (e.g. Grafana), or adding more security with fail2ban or Crowdsec. For the purposes of this article though, if it works, it’s good enough.

Caddy

Caddy 2 is a powerful, enterprise-ready, open source web server with 
automatic HTTPS written in Go”

MariaDB

MariaDB Server is one of the most popular open source relational databases”

Postgres

PostgreSQL is a powerful, open source object-relational database system with over 30 years of active development”

Web Server

Anymore, the vast majority of things you may want to self-host are web apps. While you could just expose an application to the Internet, piping it through a web server (using that server as a reverse-proxy) takes care of the common “I have all these apps and only one port/IP!” problem, as well as gives you a centralized spot for handling SSL certificates, connection logs, and so on.

If you look at usage stats, you’ll see two servers come out on top: Apache and nginx. Personally though, I absolutely love Caddy for most of my self-hosting needs. Caddy’s config is incredibly terse (Send, an application I mentioned in my Self-Hosting: Collaboration Edition article, takes 23 lines of nginx config vs 3 in Caddy) and Caddy’s native autossl functionality means literally not needing to care about setting up SSL certificates. It’s great.

Setting up Caddy

Incorporating Caddy into your ecosystem is beautifully simple. Here’s my docker-compose for it:

caddy:
  image: caddy:latest
  volumes:
    - /srv/config/caddy/Caddyfile:/etc/caddy/Caddyfile
    - /srv/data/caddy:/data
  restart: unless-stopped
  ports:
    - 80:80
    - 443:443

Breaking this down:

  • I’m using the official Caddy image from Docker Hub. If you’re smart, you’ll lock your image to a specific release. I just like to live on the edge.
  • I expose two volumes. One is a file, mapped to /etc/caddy/Caddyfile. This is the actual configuration for Caddy. The other is a folder mapped to /data, this could also be a Docker volume. /data is where Caddy stores things like the SSL certificates it generates.
  • I tell Docker that, if for some reason Caddy crashes, to restart the container unless I’ve manually stopped it.
  • I’m exposing Caddy’s container on the standard HTTP/HTTPS ports, 80 and 443. This lets Caddy handle all the public HTTP and HTTPS traffic for my server.

Before Caddy’s particularly useful though, we need a Caddyfile to actually tell the server to… serve things. Caddy’s own documentation goes into much more detail here, but for most self-hosted applications you’re proxying through Caddy, the following works just fine:

send.demo.alyx.at {
	reverse_proxy send:1443
}

This super simple configuration tells Caddy to proxy anything for send.demo.alyx.at to the send container (well, to whatever resolves to the DNS host “send”, but Docker Magic™) on port 1443. With the reverse_proxy directive, Caddy automatically handles things like websockets, X-Forwarded-For headers and so on for me.

Databases

Most of the time, applications want to take some data and use it later. To make this a sane process, they’ll store it in a database. There’s a ton of databases, but we’ll just go over two of the most common: MariaDB (MySQL) and PostgreSQL.

MariaDB and PostgreSQL are both “relational databases,” specifically SQL databases. SQL is a special language used specifically for querying/modifying structured data. Most applications are working with data that can be structured pretty well into defined tables, so they use these types of databases.

MariaDB

MariaDB is a fork of MySQL, and is (for most purposes) entirely compatible with it. Most distributions provide MariaDB as a substitute for MySQL in their repositories, and unless you specifically know otherwise, it’s probably fine to just roll with that.

mariadb:
  image: mariadb:latest
  volumes:
    - /srv/data/mariadb:/var/lib/mysql
  environment:
    - MARIADB_ROOT_PASSWORD=hunter2
  restart: unless-stopped

In this docker-compose example, I’ve:

  • Added a container for MariaDB using mariadb:latest.
  • Setup a custom volume so MariaDB data (e.g. the databases) are stored at an easy to find location on our host filesystem
  • Added an environmental variable defining the password for the MariaDB root user (Note: This should really be in an env_file so your compose.yml isn’t full of secrets, but using environment keeps this example pretty self-contained).

While I’m not exposing any ports in this example, MariaDB is a server and listens on port 3306 within its container. Generally though, it’s recommended to keep this internal and only connect other containers to it through Docker’s internal networking.

PostgreSQL

PostgreSQL is the other major free and open source SQL server, and is another very common database.

postgres:
  image: postgres:latest
  volumes:
    - /srv/data/postgresql:/var/lib/postgresql/data
  environment:
    - POSTGRES_PASSWORD=hunter2
  restart: unless-stopped

This is very similar to how we setup MariaDB, with the only real difference being we have a POSTGRES_PASSWORD this time instead MARIADB_ROOT_PASSWORD. PostgreSQL also has authentication based on Linux user account enabled, so in practice you’ll probably never use the password, but the container requires one to start anyway.

PostgreSQL is available on port 5432, but again, you’re probably best not exposing this to the internet.

Closing Thoughts

These applications, or similar ones, cover the infrastructure needed for many of the applications you may want to self-host. With this basic core, it’s rare that you’ll run into an application that’s not covered. That said, you may not always specifically want to use these — if you need more specific functionality in your web server, nginx or openresty may fit your workflow more; if you’re only running a handful of applications, you may only need MariaDB or PostgreSQL, not MariaDB and PostgreSQL, etc.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.