Setting up Caddy to serve from a GitHub hook on Debian
To take advantage of a low-resources VPS, I wanted to serve a static page but still have the ability to update that static page from a git push.
Caddy seemed like the best choice for a web server to back this. Not only is it lightweight, but it also has built-in SSL certificate setup with Let’s Encrypt.
Getting it to work was a bit of a challenge due to the general “you can do anything” Swiss army knife documentation common to open source projects. Here’s how I configured my VPS to pull from a private github repository whenever there is a push.
Install Caddy
Head to the Caddy website, and click “Download.” Pick your options, but make sure to include the git
package.
Unpack the resulting tarball into a central location - I picked /etc/caddy
and will keep using it from now on in this tutorial.
Create a user for Caddy
It’s not a good idea to run Caddy as root, so don’t. I created a new user with no home directory, and disabled its login shell:
useradd -M caddy
chsh -s /bin/false caddy
Next, you want to make sure permissions are correct. As root, let the caddy user take ownership of the /etc/caddy directory.
chown -R caddy /etc/caddy
Last, in order to bind to ports below 1024 as a ‘regular’ user, you should flag the caddy executable to enable this.
setcap cap_net_bind_service=+ep /etc/caddy/caddy
Note that you will need to re-run this command if you ever change the permissions of /etc/caddy again (e.g. you are tinkering with ownership).
Configure service with systemd
In order to run caddy as a service, you’ll want to tell systemd what is up.
I mostly followed this guide but ended up with the following caddy.service
file in /etc/systemd/system
:
[Unit]
Description=Caddy Web Server
Documentation=http://caddyserver.com
After=network.target
[Service]
User=caddy
WorkingDirectory=/etc/caddy
LimitNOFILE=4096
ExecStart=/etc/caddy/caddy -agree -email PUT YOUR EMAIL HERE
Restart=on-failure
StartLimitInterval=600
Environment=HOME=/etc/caddy
[Install]
WantedBy=multi-user.target
Don’t forget to replace the obvious all-caps section with your own email address, otherwise caddy will not be able to ask Let’s Encrypt for help getting a certificate.
Configure Caddyfile to serve a page
I wanted to make sure at this point that caddy was installed properly, so I built the following Caddyfile in /etc/caddy/Caddyfile
.
yourdomain.com {
root /www/yourdomain.com
}
Obviously, replace this with your domain.
Then I created a directory and put a test file into the directory:
mkdir -p /www/yourdomain.com
echo Hello World >> /www/yourdomain.com/index.html
chown -R caddy /www/yourdomain.com
Start caddy and test
systemd needs to be told about its new charge. You can do so like this:
systemctl enable caddy
Then you can start caddy like this:
service caddy start
Hopefully at this point all has gone well!
You should see the following things:
/etc/caddy
should now have a.caddy
directory inside it that has some SSL certificates and other gubbinsservice caddy status
should report something positive- Navigating to your domain on port 80 should return the hello world string.
Congratulations! You have a working caddy instance serving static content from an unprivileged user.
Wire to the Github webhook
In order to have the page content update whenever there is a git push, we can use the “web hook” functionality of GitHub.
Broadly, whenever you do a push (or some other git commands), GitHub will reach out and punt your web server at a place of its choosing to tell it that the repo has updated, and it should pull a new version.
Create an SSH key for deployment
If you have a private repo for your content, like I did, you’ll need to do this.
First, run the shell command ssh-keygen
and tell it to put the generated keys in /etc/caddy. Remember where those keys end up.
Next, navigate to the GitHub “Deploy Keys” page by opening your repository on the website, clicking on Settings, and then navigating to “Deploy Keys.”
Last, open up the ‘public’ key file (usually indicated by a *.pub
extension) and copy and paste the contents into the GitHub “Deply Keys” page. I made the deploy key’s permissions read-only, which is a good idea in general.
Save it, and you should now have the key you just created authorized as a deploy key.
Create a GitHub webhook
While still on the GitHub settings page for your repository, navigate to Webhooks.
Create a new webhook, pointing at your domain, such as https://mydomain.com/github-says-hello
. Remember both this URL and the secret key that you enter here.
Configure Caddy to use the GitHub repository and webhook
First, make sure the git
binary is installed. If you’re on Debian or Ubuntu it’s as easy as doing apt-get install git
. If you’re not, there is probably an equivalent for your distribution.
Open the Caddyfile and add configuration for the git plugin, so that it looks sort of like this in the end:
yourdomain.com {
root /www/yourdomain.com
git {
repo git@github.com:YOUR_GITHUB_ACCOUNT/YOUR_GITHUB_REPO_NAME
key /etc/caddy/YOUR_GENERATED_KEY_LOCATION
hook /github-says-hello YOUR_WEBHOOK_SECRET
}
}
Replace the obvious all-caps text with the secret you defined in the last step. Keep in mind if your secret has special characters, you will need to put quote marks around it.
Delete all the stuff from the directory where you created the temporary index.html
file in a previous step. It needs to be clear in order for git clone to succeed.
Restart Caddy through systemd, and check journalctl for success. It should have worked! Now you can update your git repo and have the changes magically appear a few seconds later in your caddy machine.
Troubleshooting and Snags
Can’t bind to port :80, :443
You need to re-do the setcap
command from above. It might have gotten wiped away when a permission was updated.
Can’t clone the git repo because it can’t make /home/caddy/.ssh
I had a problem where the $HOME
environment variable didn’t make it from the systemd service config for caddy all the way to the call that caddy made to git
.
My hacky solution was to use usermod -d
to reset the home directory of caddy
to be /etc/caddy
such that it had a place to put the .ssh/known_hosts
file that it made the first time it connected to GitHub. I believe this may be a bug in caddy-git or possibly just me misusing systemd.