Container updates with n8n + Gitea
I have tried many tools to manage containers. I keep coming back to Docker for the user experience. If you are not familiar with containers or Docker, check out a post for an introduction. While most containers in my homelab are now in Kubernetes, I still manage some containers with Docker compose. My DNS servers are 2 Raspberry Pi with a VM as 3rd DNS server. They also have reverse proxy servers deployed as containers (like nginx, caddy, etc.). So I built a workflow to manage those as containers. I do not want these DNS servers to be part of the k8s cluster and I want them to be able to start up without having dependencies on an external system. I want to have a functioning network if there is 1 DNS server online or all 3 are online. I am using Adguard Home to manage DNS queries in my Homelab.
I’m going to set up a workflow to automatically deploy Docker Compose apps when changes are pushed to the main
branch of a self-hosted Gitea repository. To do this, I am going to use an open source tool n8n. Check out a previous post for a less complicated introduction to n8n.
n8n workflow Setup Instructions:
Note: This workflow is tailored to my setup so the n8n UI may change slightly but I believe this guidance should you want to create a similar workflow.
1. Set Up SSH Access on Remote Server
On your Docker server, create a deploy user with Docker permissions:
# Create deploy user
sudo useradd -m -s /bin/bash deploy
# Add to docker group
sudo usermod -aG docker deploy
# Create deployments directory or use an existing
sudo mkdir -p /opt/deployments
sudo chown -R deploy:deploy /opt/deployments
2. Generate SSH Key Pair
On your n8n server (or locally):
ssh-keygen -t ed25519 -C "n8n-deploy" -f ~/.ssh/n8n_deploy_key
Copy the public key to your remote server:
ssh-copy-id -i ~/.ssh/n8n_deploy_key.pub deploy@your-docker-server.com
The other way is to put the value of the public key in this file on the deploy user’s home dir: ~/.ssh/authorized_keys
The private key will be need to be added to n8n.
3. Configure n8n SSH Credentials
In n8n:
- Go to Credentials → Add First/New Credential
- Select/search for: SSH Private Key
- Name it:
SSH Deploy Key
or for specific hosts,$hostname ssh deploy key
- Paste your private key content from
~/.ssh/n8n_deploy_key
- Save it
4. Create Workflow
This next step is to create the workflow. Here I am using Discord notifications at the end but that could just as easily be swapped for Slack.
Create a new workflow in n8n and add the following nodes:
a. Webhook Node
- Add a Webhook node
- Set HTTP Method to
POST
- Set Path to
gitea-webhook
- Set Respond to “Using ‘Respond to Webhook’ Node”
b. IF Node - Check Main Branch
- Add an IF node
- Set Value 1 to
{{ $json.body.ref }}
- Set Operation to “Equals”
- Set Value 2 to
refs/heads/main
c. Respond to Webhook - Ignored
- Add a Respond to Webhook node connected to the FALSE output
- Set Respond With to “JSON”
- Set Response Body to:
{{ { "status": "ignored", "message": "Not main branch", "branch": $json.body.ref } }}
d. SSH Node - Update Repository
- Add an SSH node connected to the TRUE output
- Select your SSH credential
- Set Command to:
cd /opt/containers && ./update-containers.sh || exit 1
- Set Working Directory to
/home/deploy
e. IF Node - Check Success
- Add another IF node
- Set Value 1 to
{{ $json.code }}
- Set Operation to “Equals” (number)
- Set Value 2 to
0
f. Discord Node - Success (Optional)
- Add a Discord node connected to the TRUE output
- Set Content to:
Docker n8n deployment completed successfully from Gitea repo: {{ $('Check Main Branch').item.json.body.repository.full_name }}
g. Discord Node - Failure (Optional)
- Add a Discord node connected to the FALSE output
- Set Content to:
**ERROR:** Docker n8n deployment failed from Gitea repo: {{ $('Check Main Branch').item.json.body.repository.full_name }}\n\nSee output:\n{{ $('SSH: Update Repository').item.json.stderr }}
5. Set Up Gitea Webhook
In your Gitea repository:
- Settings → Webhooks → Add Webhook → Gitea
- Target URL: Your n8n webhook URL
- HTTP Method:
POST
- Content Type:
application/json
- Trigger On: Push events
- Save
Make sure that you configure Gitea to allow a webhook connection from n8n. There is an env var ALLOWED_HOST_LIST
that you need to update to use webhooks.
6. Test the Deployment
Test the webhook by using the “Test Delivery” button in the Gitea webhook settings. N8n has a test webhook endpoint that can also be used to play around with the workflow before you activate the workflow for “production”.
Activate the workflow and push a change to your main branch when everything looks good.
What This Workflow Does:
- Receives webhook from Gitea on push
- Checks if main branch (ignores other branches)
- Runs deployment script via SSH (
./update-containers.sh
) - Checks exit code to determine success or failure
- Sends Discord notification with deployment status
- Responds to Gitea with appropriate status
Discord notifications are optional. Success notifications can be disabled if too noisy. Here is an example notification in a channel. Slack would use a similar format:
Update container script
Instead of having n8n or another system run a bunch of commands, here is a bash script that will pull changes from git remote origin and update each docker compose file in a single step. The script assumes we start in a top level directory and that each docker compose stack is a sub-directory with a yaml file.
Assuming that we put our git repo in /opt/containers, create a script in the git repo update-containers.sh
to match the invocation in the n8n SSH command step.
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${ROOT_DIR}"
echo "Updating repository..."
git pull --ff-only
for compose_file in "${ROOT_DIR}"/*/docker-compose.yml; do
svc_dir="$(dirname "${compose_file}")"
svc_name="$(basename "${svc_dir}")"
echo
echo "Running docker compose for ${svc_name}..."
(
cd "${svc_dir}"
docker compose pull
docker compose up -d
)
done
echo
echo "Deployment complete."
This script can be executed on the terminal or by n8n.
Additional Tips:
For private Gitea repositories, add a deploy key on the remote server to facilitate the git pull
command in the SSH deployment.
ssh deploy@your-docker-server.com
ssh-keygen -t ed25519 -f ~/.ssh/gitea_deploy
cat ~/.ssh/gitea_deploy.pub
Add the .pub (public key) to Gitea repo → Settings → Deploy Keys
Test SSH connection from n8n:
ssh -i ~/.ssh/n8n_deploy_key deploy@your-docker-server.com
Multiple servers? Duplicate the workflow and change the host in each SSH node to deploy to different servers.