The first iteration of my personal ‘server’ was organized and administered in a traditional manner on an instance of Fedora 24. I ran into a few problems that necessitated a change.

My system was fragile and not very consistent in how it was maintained. Applications were strewn all over the file system from different packaging systems. Official Fedora packages would install to /usr/share, self-contained web applications were installed in /opt, and PHP apps were installed in /var/www. Updates were annoying, largely a manual process, and extremely prone to breakage.

The other big issue is that even on Fedora the packages were still to old for my tastes. I recognize that the use case of a single-user bleeding edge is not exactly typical but it nonetheless did not meet my needs.

My Services

I’m asking a lot of my small server, here are just a few of the services I ask my small 512Mb server to run.

  • This site — static web hosting with Jekyll.
  • My Personal Docs — wiki software with Dokuwiki.
  • My VPN — a private VPN with OpenVPN.
  • IRC Bouncer — IRC bouncer with ZNC.
  • Source Control — project management with Gogs.
  • Calendar & Contacts — CalDAV & CardDAV with baikal.

Organization

So how do we organize our server to support all these services? We also need to have full SSL support.

Here’s what I came up with and it seems to be working reasonably well. I’m sure I could accomplish something clever with Docker’s internal networking this is simple, easy to maintain, and has been rock solid for the last few months. We run local instances of Nginx and Certbot which act as a reverse proxy and SSL terminator. Then each service exposes its web port internally to be reachable by the reverse proxy.

                          +--------------------+  +---------------+
                     +----| inspiredby.es:1000 |--| docker:jekyll |
                     |    +--------------------+  +---------------+
                     |
+---------------+    |    +-------------------------+  +-----------------+
| reverse proxy |----+----| wiki.inspiredby.es:2000 |--| docker:dokuwiki |
+---------------+    |    +-------------------------+  +-----------------+
                     |
                     |    +------------------------+  +-------------+
                     +----| git.inspiredby.es:3000 |--| docker:gogs |
                          +------------------------+  +-------------+

System Updates

Since all of our services are contained in docker images we can safely update the base system with little risk of breakage or service interruption. On CentOS 7 we can use the package yum-cron package to accomplish this. We can set this whole thing up with the following.

yum install -y yum-cron
# /etc/yum/yum-cron.conf

# Select all updates.
update_cmd = default

# Download updates.
download_updates = yes

# Install updates.
apply_updates = yes
systemctl enable --now yum-cron.service

Service Updates

When it comes to patching the actual services Docker doesn’t provide a seamless update mechanism but using Docker primitives we can put together something that’s good enough.

+------------+
| Image v1.0 |
+------------+       +-------------+
                 +---| Data Volume |
+------------+   |   +-------------+
| Image v1.2 |---+
+------------+

Just as an example say we’re trying to update the Nginx image that’s hosting this site. The update procedure we would follow is

# Stop the container.
docker stop estheruary

# Remove the container.
# Don't pass the -v flag as it also removes attached volumes.
docker rm estheruary

# Update the image.
docker pull nginx

# Start a new container with the updated image.
docker run --name=estheruary -v estheruary-data:/usr/share/nginx/html:ro -p 2000:80 -d nginx

Proxying Requests

Once we have the web server running we need to configure our reverse proxy to route outside requests. Continuing our example here’s the configuration for this site.

server {
        listen 80;
        server_name inspiredby.es;

        return 301 https://$host$request_uri;
}

server {
        listen       443 ssl http2;
        listen       [::]:443 ssl http2;
        server_name  inspiredby.es;

        ssl_certificate "/etc/pki/tls/certs/estheruary.crt";
        ssl_certificate_key "/etc/pki/tls/private/estheruary.key";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        include /etc/nginx/default.d/*.conf;

        location /.well-known {
                alias /var/www/estheruary/.well-known;
                allow all;
        }

        location / {
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_pass http://localhost:2000;
        }
}

Conclusion

Overall I’ve been very happy moving to Docker for all of my services. I have had more stability, less maintenance, and newer software to play with.