· Alexander · GitHub · 8 min read
GitHub Actions - Automatically Deploy Docker Updates to Cloud Hosts via SSH
Automatically deploy docker updates to your cloud hosts using GitHub Actions and SSH

This is part 3 of this series so if you haven’t completed part 1 and part 2 yet, go back if you want to follow along. In this part we will be updating our Caddy pipeline to deploy updates from Actions to a VM running in Linode’s cloud. As we are using Docker to run Caddy this will be very easy. In Part 4 we will be building a binary and pushing it to the Linode VM instead, which is slightly more complicated.
The Environment
I will be running this series as if you were running on Windows 10/11 with Visual Studio Code installed. Now this series can also be followed fairly easily if you are running Linux setup or WSL. I am also assuming you have a general understanding of the command line interface. If you need more help, leave a comment below and I will reach out!
Pre-Requisites
You should have these items completed and set up before trying to follow this post.
- Created a Linode Account
- You can use my affiliate link to open an account and get credit. It helps support the blog! :)
- Have a Linux VM running in Linode’s Cloud with Docker Engine installed.
- Have SSH Key Auth setup on the VM.
- Have a secondary SSH key pair set up.
- Have followed the previous1 parts2 in this series.
- Have a Domain registered and an A record pointed to the public IP of your Linode VM.
- I use Google Domains with DNS handled by Cloudflare.
Setting up the VM
First we need to create a new user account on our VM so Actions is not logging in as root. To do this, first SSH into your VM as root (since we haven’t setup any other users yet) and run the following commands to create a new non-root user.
ssh i- /path/to/private_key root@host# Create a new user called "actions" with a home directory (-m).useradd -s /bin/bash -m -c "GitHub Actions Account" actionsAfter the command completes, create a password for the new account.
passwd actionsFollow the prompts to set a new password for the actions account. Once done, switch to the new user and verify the home directory was created properly.
su actions && cd ~ && ls -a -lYou should see some default hidden files that were created:
total 20drwxr-xr-x 2 actions actions 4096 Jul 6 14:55 .drwxr-xr-x 4 root root 4096 Jul 6 14:55 ..-rw-r--r-- 1 actions actions 220 Feb 25 2020 .bash_logout-rw-r--r-- 1 actions actions 3771 Feb 25 2020 .bashrc-rw-r--r-- 1 actions actions 807 Feb 25 2020 .profileNow create the .ssh directory structure for the actions user.
mkdir .sshtouch .ssh/authorized_keysSet permissions on the .ssh directory.
chmod 700 .sshchmod 600 .ssh/authorized_keysNow Add the second key pair’s public key to the .ssh/authorized_keys file. From your PC run the following commands depending on the OS:
Windows 10/11
type $env.USERPROFILE\.ssh\mykey.pub | ssh actions@host "cat >> .ssh/authorized_keys"Linux
ssh-copy-id -i ~/.ssh/mykey.pub actions@hostYou can then test to make sure your SSH key auth is working.
ssh -i ~/.ssh/mykey actions@hostMake sure to update the path to the public key file and the @host to the public IP/DNS name of your VM.
Once we have verified that our user can login via SSH, we can disable password auth for the VM. Switch back to root and edit the /etc/ssh/sshd_config file.
su root# `exit` can also be usednano /etc/ssh/sshd_configHit CTRL+W to enter search and type PasswordAuthentication and hit Enter. Change the value from yes to no. Hit CTRL+X then y to save and close the file. Now restart the sshd service so the changes take affect.
systemctl restart sshdCreating the File Structure
Now we need to setup and create the directory structure for Caddy on the Linode VM. Run the following commands to setup the directory and file structure for Caddy.
mkdir ~/caddymkdir ~/caddy/datamkdir ~/caddy/Caddyfilemkdir ~/caddy/docker-compose.ymlNow edit the docker-compose.yml file:
nano ~/caddy/docker-compose.ymlCopy the following into the docker-compose file. Make sure to change <username> to your Docker Hub username. If you used GitHub’s container repo, make sure to add ghcr.io/ in front of your username, which should be your GitHub one (e.g. ghcr.io/alexandzors/caddy).
version: '3.6'services: caddy: restart: always logging: driver: "json-file" options: max-size: "500k" max-file: "1" image: <username>/caddy ports: - 80:80 - 443:443 volumes: - ~/caddy/config/Caddyfile:/etc/caddy/Caddyfile:ro - ~/caddy/config:/config - ~/caddy/data:/dataSave and close the file by hitting CTRL + X then y.
Now edit the Caddyfile and add the following content (same quick start from Caddy’s docs):
nano ~/caddy/config/Caddyfile#Caddyfileyourdomain.tld
respond "Hello World"Save and close the file by hitting CTRL + X then y. We can leave the current SSH session open as we will be needing it later.
Updating the Actions pipeline
Now that we have the VM file structure ready to go, we can update our GitHub Actions pipeline to automatically update our docker container on image updates. Navigate to our actions repository and open the actions.yml file with your favorite text editor. Now add the following lines at the end of the file:
[Previous YAML content...]Once you are done editing, save the file.
Updating Actions Secrets
With our actions pipeline file updated, we need to update our secrets to match. Navigate to your repository on GitHub.com and click on the Settings tab > Secrets > Actions, then create the following secrets:
| Name | Value |
|---|---|
| LINODEhost | public ip / dns name of your Linode VM |
| LINODEuser | username for login |
| LINODEkey | SSH secret key (use the second pair’s secret key not the one you used to deploy the host with.) |
Updating the Repository
Now that we have the secrets created we can update our repository.
git add .git commit -m "set actions to update linode vm"git pushAfter the push our actions pipeline will automatically kick off. To watch the log, return to the repository on GitHub and click on the Actions tab. Under “All Workflows” click on Caddy. The newest run should show up as a yellow flashing dot. Click on the item title to view the pipeline and follow the log output. When it completes we can run a curl check on our webserver to verify its running.
curl https://yourdomain.tldIf successful, you should get a Hello World response. If it fails, return to your SSH session and first check that Caddy is running using docker ps.
Docker ps output showing Caddy container running
If it is not, cd to the caddy directory and manually run the container.
cd ~/caddydocker compose upIf it starts up with no errors, terminate the container using CTRL+C. Then double check your action’s file commands for spelling mistakes during the container running portion under the “Deploy to VM” step. As always, if you run into issues, let me know in the comments or reach out via email and I’ll gladly walk you through it! Check out part 4 where we change up the pipeline to build and release a binary version of Caddy.
Thanks to my good friend Stefan for helping proof this post series!