Hosting a site with docker + nginx + certbot + wildcard certs
I recently reconfigured my website to use Docker instead of installing everything manually. The main challenges I wanted to overcome are automating the certificate generation, sandboxing everything enough to not cause security issues, issuing wildcard certs with DNS challenges, and doing it all through docker to make updates and migrations consistent and easy.
I’ve seen several guides on setting up nginx and certbot using docker, however almost all of them use the HTTP acme challenge instead of the DNS challenge, which is easier to set up, assuming your DNS server is supported. DNS challenges are also required for issuing wildcard certs.
This guide is not supposed to hold your hand but rather function as a reference for setting up this scenario. It is expected that you have basic experience with all these things already.
Basic setup
You’ll need several prerequisites for this. First and foremost, you’ll need access to a linux server running your distro of choice. You’ll want to make sure your DNS is already set up to point at it, and configure it up to the point that you’d start doing your traditional installs of stuff like nginx and certbot. Your DNS service will also need to be one of the ones supported here. You will also want to check the docs for your appropriate plugin to make sure you get your API keys configured properly.
Beyond that, you’ll want to have docker and docker-compose.
For context, I am using ubuntu server 18.04 on a linode VPS, and the docker apt repos.
Getting certs
The first thing we want to do is get our certs. We can do this using the letsencrypt docker image and docker-compose. I’ll start with my
docker-compose.yml
and break it down from there.
We can see there’s a number of things happening here. First, is the image. You can see that I’m using the dns-linode
image instead of the
regular certbot
image. Depending on which DNS plugin you need to use, you will need to select the appropriate image. You can find a list
of them on the certbot dockerhub page.
Obviously, we don’t want our certs to live inside the container or nothing can use them.
I’ve exported a volume to /data/certbot/conf/
which will allow us to read the certs from other locations. There are some permissions issues
here, but we will solve those soon.
The next thing to look at is the options for certbot. We want to use certonly since we don’t want to install configs for nginx etc. We also
need to specify the non-interactive options with -n --agree-tos -m email@here.com
. For the DNS options, these might vary based on which
service you use, but they should be fairly similar. The credentials file is stored in /data/certbot/conf as linode.ini, and mind looks
something like this:
This config will vary based on your provider, check the docs. One important thing you will want to include however is that rspserver
line.
This specifies the acme V2 challenge API, which will allow you to issue wildcard certs. This may not be necessary in the future but my
previous attempts failed without this line. One very very important step is to set the permissions on this since it contains your API keys.
I set mine with chown 700
. Certbot will complain if you have anything less, as it should.
The second to last flag is the propogation time, which allows us some leeway for DNS records to propogate. Once again, check the docs for your plugin.
Finally, the domains. We want to grab our base domain, in my case mikesbytes.org
and the wildcard, *.mikesbytes.org
. That way if we want
to host another site or service under a subdomain, we don’t have to generate new certs.
Once you have that stuff set up, you’ll want to run docker-compose up
and wait for your certs to generate.
Running NGINX in docker as a non-root user
Now that we have our certs, we want to configure our actual site. We could easily spin up a plain old nginx image, however doing this is not the most secure as it will run as the root user
Let’s take a look at the docker-compose.yml
I’ve created for nginx
The first thing to note is that this does not use the stock nginx image. We have to modify it to make it work as a non-root user, but before
that we need to actually create the non-root user. I created a user called www
with the UID/GID of 1001
on my system. We’ll want to
run nginx as this user. There is a user
config option for docker-compose, however it won’t work with the stock nginx image, so let’s take
a look at the Dockerfile I created in the nginx-docker-container
folder.
We can see this does a few things. First, it copies a custom nginx.conf
file in, which looks like this
You can modify this however you want. Don’t put your actual site configs in here though, those go in .../nginx/conf.d
.
It also adds the www user to the container, and modifies the permissions on the appropriate files for access. It also directs the container to run as that user.
Configuring a site
At this point, nginx should run, but it won’t have your site. We need to configure one. I create a file in /data/nginx/default.conf
that
looks like this:
Some things to note here are the ports. Since we aren’t running as root, we can’t bind to lower number ports, but we redirected them in
docker-compose. You’ll want to modify the server_name
s, and cert paths. This config also auto-redirects HTTP requests to HTTPS.
At this point you’re almost there, however if you try to run your nginx server with docker-compose, you’ll get permission denied errors
on your certs because the default perms disallow reads for non-root users. The easiest fix for this is to run
chmod 755 /data/certbot/conf/live
which will grant read perms to other users. A slightly better option would be to create a certs
group on your system, add the root user and your www user to it, chown the live
folder with root:certs, and enable only group read instead
of group and other read.
At this point, you should be able to spin up your nginx container and browse to your site, which leaves only one more thing.
Cert renewals
The full docker-compose file I use looks like this:
You can see there’s some junk in the command
and entrypoint
options. These will have certbot check for renewals every 12 hours, and nginx
will refresh it’s config every 6 hours. I got these from the guide located here.
And with that, we have basically the setup you’re currently browsing this site on.