|
| 1 | +# caddy-docker-proxy-redis |
| 2 | + |
| 3 | +## How I do run my linux servers |
| 4 | + |
| 5 | +I do have a few linux virtual servers spread around in various data |
| 6 | +centers. Originally that was mostly nginx front ends to various locally |
| 7 | +installed applications, all supervised by systemd. Changing to caddy |
| 8 | +as the proxy server was a fresh breaze as the configuration got much |
| 9 | +simpler and there where no shell scripts needed any longer to update |
| 10 | +certificates with letsencrypt. Still a lot installed on each server, but |
| 11 | +simpler. |
| 12 | + |
| 13 | +My most recent iteration in making the administrative side easier is the |
| 14 | +following setup: |
| 15 | + |
| 16 | +* All nodes run tailscale as an overlay VPN for admin functions. |
| 17 | +* All nodes run docker and one instance of the watchtower container |
| 18 | + for easy automatic upgrades of the individual packages. |
| 19 | +* Docker is configured to log to Google Cloud Logging for easy log file |
| 20 | + monitoring. This is may not be relevant for most people, but for me |
| 21 | + it makes perusing logs much easier. |
| 22 | +* All docker containers are started via a docker-compose.yml file. Each |
| 23 | + of those gets their own subdirectory with the docker-compose.yml |
| 24 | + file alongside any additional configuration and data volumes needed. |
| 25 | +* I run caddy with the docker-proxy and caddy-tls-redis plugins in a |
| 26 | + container as front end proxy. |
| 27 | +* Individual containers for the services use caddy docker proxy label |
| 28 | + fragments for configuration in the individual docker-compose.yml |
| 29 | + files. |
| 30 | +* All caddy instances share a common storage backend (a redis instance |
| 31 | + reached via the tailscale overlay vpn) for tls certificate storage. |
| 32 | + This facilitates easy moving a service from one server box to another |
| 33 | + without a lot of downtime due to missing TLS certificates. |
| 34 | +* I use an instance of gatus to monitor all these services and get |
| 35 | + notified by email about any failures. The caddy health check is on |
| 36 | + the tailscale side, so I check both the status of the tailnet as |
| 37 | + well as caddy basics working in one check. |
| 38 | + |
| 39 | +All the referenced docker-compose files below assume a docker bridge |
| 40 | +network named caddy, see the networks.sh script. |
| 41 | + |
| 42 | +I do override configuration on the docker service via creating the |
| 43 | +directory /etc/systemd/system/docker.service.d and creating a file |
| 44 | +override.conf like this: |
| 45 | + |
| 46 | +``` |
| 47 | +[Service] |
| 48 | +After=tailscaled.service |
| 49 | +Environment="GOOGLE_APPLICATION_CREDENTIALS=/home/amdinuser/.serviceaccts/hosting-XXXXXX-XXXXXXXXXXXX.json" |
| 50 | +``` |
| 51 | + |
| 52 | +The After= section makes sure that docker starts after tailscale is |
| 53 | +innitialized. GOOGLE_APPLICATION_CREDENTIALS injects the credentials of |
| 54 | +a service account that has log and error reporting permissions on a |
| 55 | +Google Cloud project. I modify the docker daemon config in |
| 56 | +/etc/docker/dameon.json like this: |
| 57 | + |
| 58 | +``` |
| 59 | +{ |
| 60 | + "log-driver": "gcplogs", |
| 61 | + "log-opts": { |
| 62 | + "gcp-project": "hosting-XXXXXX", |
| 63 | + "gcp-meta-name": "myservername" |
| 64 | + } |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +The Google Cloud configuration is optional if you like to use journalctl |
| 69 | +on the individual hosts. |
| 70 | + |
| 71 | +## Caddy |
| 72 | + |
| 73 | +The root directory of this repo contains the Dockerfile and a |
| 74 | +build-docker.sh script to build the container that runs caddy with the |
| 75 | +docker proxy and tls-redis plugins. I do build both AMD64 and ARM64 |
| 76 | +versions of each of my containers as my linux systems use both of these |
| 77 | +architectures. The caddy subdirectory showcases a typical caddy |
| 78 | +configuration. I do run caddy in its container with ports forwared for |
| 79 | +port 80 and 443 TCP and 443 UDP for QUIC aka HTTP/3. For easier |
| 80 | +configuration of the individual services I do include Caddyfile snippets |
| 81 | +in the config/Caddfile subdirectory. The caddy docker-proxy |
| 82 | +configuration to build a final Caddyfile can get a bit obscure for more |
| 83 | +complicated containers like nextcloud. |
| 84 | + |
| 85 | +This Caddyfile also defines a https site on the tailscale side for the |
| 86 | +host, this has by default just a /health endpoint for health checking. |
| 87 | +The watchtower and adminer containers do add subdirectory endpoints here |
| 88 | +for the watchtower API and for examining database content. |
| 89 | + |
| 90 | +The volume /run/containers for caddy might look unfamilar, but I use |
| 91 | +this to expose my own services via unix domain sockets. If you do not |
| 92 | +expect to proxy to upstream servers via unix domain sockets you might |
| 93 | +omit that volume. |
| 94 | + |
| 95 | +## Watchtower |
| 96 | + |
| 97 | +The container defined in the watchtower subdirectory is responsible for |
| 98 | +updating the containers actually running on the host, with the exception |
| 99 | +of watchtower itself. Lettings watchtower update itself does not appear |
| 100 | +to be working, but fortunately this is mature and changes seldomly. |
| 101 | +Please note that you should set a random password for the watchtower API |
| 102 | +in this docker-compose.yml. This container also needs to access your |
| 103 | +docker credentials in your home directory for accessing your docker |
| 104 | +repositories. |
| 105 | + |
| 106 | +## Whoami |
| 107 | + |
| 108 | +For debugging (and as a placeholder to aquire certificates) I tend to |
| 109 | +start an instance of whoami on the canonical host name of the linux box. |
| 110 | +For hosts that may be running postfix and dovecot or other services |
| 111 | +outside the caddy/docker universe a seperate project in |
| 112 | +github.com/jum/certwatch can be used to monitor a set of certificates in |
| 113 | +the caddy redis storage and write the certificates to /var/lib/certwatch |
| 114 | +and restart systemd services. |
| 115 | + |
| 116 | +## Adminer |
| 117 | + |
| 118 | +Adminer is a nice tool to examine mariadb or postgres databases. I do |
| 119 | +only run it exposed on the tailscale host name under the /adminer |
| 120 | +endpoint, and then only when I need it. |
| 121 | + |
| 122 | +## Redis |
| 123 | + |
| 124 | +One node in the tailscale VPN should run redis for storing the TLS |
| 125 | +certifcates. The redis subdirectory contains an example for this, please |
| 126 | +note that this also has to expose the tailnet side of the hosts IP (the |
| 127 | +100.X.X.X IP in the docker-compose.yml file for use in redis URLs. |
| 128 | + |
| 129 | +## Databases |
| 130 | + |
| 131 | +The postgres and mariadb subdorectories contain docker-compose files to |
| 132 | +start any of these databases. I have used mariadb in the past but on new |
| 133 | +projects I am mostly using postgres. Please note that for both databases |
| 134 | +you would need to change the IP numer 100.X.X:X to be able to access |
| 135 | +these databases from anywhere inside you tailnet. |
| 136 | + |
| 137 | +## Backup |
| 138 | + |
| 139 | +I do run nightly backups via restic to a Hetzner storage box via SFTP. |
| 140 | +After configuring one of those and putting these credentials into your |
| 141 | +.ssh/config and into /root/.ssh/config as well I run using a script like |
| 142 | +this: |
| 143 | + |
| 144 | +``` |
| 145 | +#!/bin/sh |
| 146 | +export PATH=$PATH:/usr/local/bin |
| 147 | +
|
| 148 | +if test -t 0 |
| 149 | +then |
| 150 | + VERBOSE= |
| 151 | +else |
| 152 | + VERBOSE=-q |
| 153 | +fi |
| 154 | +export RESTIC_REPOSITORY=sftp:storage:backup-hostXXX/restic |
| 155 | +export RESTIC_PASSWORD_FILE=$HOME/.restic-backup-hostXXX |
| 156 | +
|
| 157 | +sudo -u adminuser sh -c "cd ~/web/redis; ./dumpdb.sh" |
| 158 | +sudo -u adminuser sh -c "cd ~/web/mariadb; ./dumpdb.sh" |
| 159 | +sudo -u adminuser sh -c "cd ~/web/postgres; ./dumpdb.sh" |
| 160 | +
|
| 161 | +restic $VERBOSE backup --exclude-caches=true \ |
| 162 | + /etc/docker /var/lib/docker \ |
| 163 | + /usr/local \ |
| 164 | + /home/adminuser /root |
| 165 | +restic $VERBOSE forget \ |
| 166 | + --keep-daily 7 --keep-weekly 5 --keep-monthly 12 \ |
| 167 | + --keep-yearly 100 --prune |
| 168 | +
|
| 169 | +chown -R adminuser:adminuser /home/adminuser/.cache/restic |
| 170 | +``` |
| 171 | + |
| 172 | +This scripts assumes that the subdirectories for the docker containers |
| 173 | +are kept under ~adminuser/web and that the adminuser is a member of the |
| 174 | +docker group. |
0 commit comments