Deploying to Dokku with Docker Images from GitHub Actions
This website is deployed using Dokku.[1]
While it's possible to deploy to a Dokku host by pushing code directly from Git, the server running Dokku in my case doesn't have enough capacity to handle application builds. I wanted to avoid building the application directly on the Dokku host.
To solve this, I decided to use GitHub Actions to build Docker images and deploy those images to Dokku. After some trial and error, I’ve documented the process here for reference.
Below is the workflow file for the GitHub Action I currently use:
The process flow is as follows:
- Check for code changes
- If the app code is updated:
- -> Set
steps.changes.outputs.code == true
- -> Set
- If entry files are updated:
- -> Set
steps.changes.outputs.entries == true
- -> Set
- If the app code is updated:
- If entry files are updated:
- Sync entry files using rsync
- If the app needs an update:
- Build a Docker image
- Upload the built image to the GitHub Container Registry
- Deploy the image to Dokku
- If only the entry files were updated:
- Restart the service in Dokku to reload the entries
Filtering types of repository changes
The trickiest part of creating this workflow was detecting the type of code changes. This was necessary because not all commits require redeploying the application.
In my case, both application code and blog entry files are stored in the same repository. I wanted to support scenarios where only entries are updated without redeploying the application.
Initially, I attempted to handle this manually, but it almost drove me crazy. Thankfully, the dorny/paths-filter action saved the day. This is truly a fantastic action.
Handling this manually would have required detecting changes across various scenarios, such as single commits (I allow direct commits to the main branch for entry updates) and pull request merges and etc. I deeply appreciate the effort of those who made this action available.
Executing Dokku commands from GitHub Actions
Most Dokku commands can be executed over SSH, so enabling SSH access to the Dokku host from GitHub Actions is sufficient.
First, generate an SSH key pair:
$ ssh-keygen -t ed25519 -f dokku
This generates a private key (dokku
) and a public key (dokku.pub
).
Register the public key on the Dokku host:
$ echo "your pubkey here" | dokku ssh-keys:add github
$ echo "your pubkey here" >> ~/.ssh/authorized_keys
The first line is required for using Dokku commands, while the second is for rsync operations. If you only need to run Dokku commands, the first line alone is sufficient.
Next, register the private key on GitHub:
I registered it as DOKKU_SSH_KEY
.
Finally, add a step to place the key in the appropriate location so subsequent steps can access the Dokku host via SSH:
- name: Set up SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DOKKU_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H typester.dev >> ~/.ssh/known_hosts
Building and uploading Docker images to GitHub Container Registry
This step was straightforward and mostly followed official documentation.
Deploying Docker images to Dokku
The deployment step looks like this:
- name: Deploy image to production
if: steps.changes.outputs.code == 'true'
run: |
ssh dokku@typester.dev git:from-image typester.dev ghcr.io/typester/typester.dev@${{ steps.push.outputs.digest }}
Initially, I set the image target as something like typester.dev:main
, but if the image existed locally, it wouldn't pull the updated one. To fix this, I started specifying the digest value from the previous build step.
Restarting the service
When only entries are updated, restarting the service reloads the entries. The following step handles this:
- name: Restart production server to reflect entries update
if: steps.changes.outputs.code != 'true' && steps.changes.outputs.entries == 'true'
run: |
ssh dokku@typester.dev ps:restart typester.dev
Both the image deployment and service restart steps ensure zero downtime by switching traffic to the new service only after it is up and running by Dokku. This is excellent!
Conclusion
Dokku is amazing.