If you start looking around the internet there are tons of different articles about getting this setup. They all vary in complexity and at times get a bit confusing. I’ve gone down this path before without Docker setting up an Ubuntu instance on Digital Ocean and installing everything from scratch. It was a complete nightmare, but after many many hours or days I was able to get it working. Once I started to understand Docker and had everything running locally at home it seemed like it would be a much easier to maintain there.

I ditched my Digital Ocean droplet and started researching how to do this in Docker on my home server. There was one requirement, which was I need a container that supported the DNSimple DNS plugin since I host my sites through DNSimple. It turns out there is an absolutely beautiful container linuxserver/letsencrypt that does everything I needed. It supports all the various plugins for certbot.

Prerequisites

The first thing I did was add an A record with the actual domain (example-domain.com), and a wildcard subdomain (*.example-domain.com) to DNS and pointed it at my home ip. I then forwarded ports 80 and 443 to my home server.

docker-compose.yaml

Below is the Docker Compose file I setup. Its pretty much copy and paste from their example. The main things to point out are: SUBDOMAINS=wildcard, VALIDATION=dns, and DNSPLUGIN=dnsimple.

version: "3.7"
services:
  letsencrypt:
    image: linuxserver/letsencrypt
    container_name: nginx-letsencrypt
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Los_Angeles
      - URL=yourdomain.com
      - SUBDOMAINS=wildcard
      - VALIDATION=dns
      - DNSPLUGIN=dnsimple 
      - EMAIL=name@email.com
    volumes:
      -  config:/config
    ports:
      - 443:443
      - 80:80 #optional
    restart: unless-stopped
volumes:
  config:
    name: nginx_letsencrypt
    driver: local-persist
    driver_opts:
      mountpoint: ${LOCAL_PERSIST}/nginx/config

Once that’s saved, you just need to run docker-compose up -d

DNSimple Configuration

After the container is running you’ll need to go modify the configuration for the DNSimple plugin and put your token in there. To get this token you’ll need to go to your DNSimple Account page and click the Automation tab on the left. Then under API Tokens you’ll click the new button, give it a name, and copy the token.

Now that you have the token your going to navigate to config/dns-conf/dnsimple.ini which is wherever you pointed your volume to and paste that token in replacing the default one thats in there. Once you’ve saved that file you can then restart the container with docker-compose restart At this point you should now be able to navigate to your url and will be presented with the default page.

NGINX Subdomain Config

Next thing I did was configure a subdomain to point to my Home Assistant install. I used the default example that they provide in the documentation for the container and also this post with a few minor changes/additions.

server{
  listen 443 ssl;
  listen [::]:443 ssl;
  server_name ha.*;

  include /config/nginx/ssl.conf;

  client_max_body_size 0;

  location / {
    include /config/nginx/proxy.conf;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    resolver 127.0.0.11 valid=30s;
    set $upstream your.ip.here:8123;
    proxy_pass http://$upstream;
  }
}

I’ll call out the key changes that I made. For server_name you can enter your subdomain.*. Next you’ll need to add proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection “upgrade”;. Then finally you’ll need to change your.ip.here to be the internal IP of the machine hosting Home Assistant. Once this is all setup the final thing left to do is run docker-compose restart and you should be up and running.

But my IP is dynamic and changes

Your home IP is most likely dynamic and could change at anytime. Obviously this will cause issues, and everything we’ve setup will break since that A record will no longer point to the correct place. DNSimple provides an easy solution to this problem. They provide a shell script for updating DNS with your current IP using the same token approach that the dns plugin for DNSimple that Certbot uses.

#!/bin/bash

TOKEN="your-oauth-token"  # The API v2 OAuth token
ACCOUNT_ID="12345"        # Replace with your account ID
ZONE_ID="yourdomain.com"  # The zone ID is the name of the zone (or domain)
RECORD_ID="1234567"       # Replace with the Record ID
IP=`curl --ipv4 -s http://icanhazip.com/`

curl -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -H "Accept: application/json" \
     -X "PATCH" \
     -i "https://api.dnsimple.com/v2/$ACCOUNT_ID/zones/$ZONE_ID/records/$RECORD_ID" \
     -d "{\"content\":\"$IP\"}"

For TOKEN it’s the same process as before. I’m pretty sure you can use the same one generated previously, but I chose to generate a new one. The ACCOUNT_ID I grabbed from the URL when logged into DNSimple. ZONE_ID is obviously the domain being updated. The RECORD_ID I found by clicking on edit for a DNS record, and then pulling the ID from the URL.

Automate the Script

Once I got that script sorted out, I needed a way to get it to run regularly to make sure the IP was up to date. I opted for creating a Docker container with this being its sole responsibility. Obviously this could just be a cron job you ran on the machine, but what fun would that be?

version: '3.7'
services:
  alpine:
    build: .
    container_name: "dnsimple-dyndns"
    environment:
      - TZ=America/Los_Angeles
    image: "dnsimple-dyndns"
    restart: unless-stopped
docker-compose.yaml
FROM alpine:3.11
RUN apk add curl
COPY example.dyndns.update-dns /etc/periodic/15min/dnsimple-updatedns
CMD crond -l 2 -f
Dockerfile
#!/bin/sh

TOKEN="your-token"  # The API v2 OAuth token
ACCOUNT_ID="your-account-id"        # Replace with your account ID
ZONE_ID="example.com"  # The zone ID is the name of the zone (or domain)
RECORD_ID="your-record-id"       # Replace with the Record ID
IP=`curl --ipv4 -s http://icanhazip.com/`

curl -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -H "Accept: application/json" \
     -X "PATCH" \
     -i "https://api.dnsimple.com/v2/$ACCOUNT_ID/zones/$ZONE_ID/records/$RECORD_ID" \
     -d "{\"content\":\"$IP\"}"
update-dns

I created the Dockerfile from alpine:3.11. I installed curl so that the script could execute the command. Under /etc/periodic/15min you can drop any scripts you want run and cron will kick them off. I copied the script in there, and then finally need the container to run the command crond -l 2 -f. Thats really all there is to it, so all that was left was to run docker-compose build and then docker-compose up -d and its up and running.