Hi there! I have here another update on selfhosting. I hosting all services with docker and using docker-compose.yml file to deploy services. I wrote about it more in Notes on Selfhosted Services. Ok, now i have one repository for configuration and each application has it own repository where I have source codes and where i building image. All my sorce codes are stored on GitLab and all jobs runs on Gitlab ci after pushing to repository.
How looks my workflow?
- Pull changes from original repository
- Merge it to my production branch on my server
- CI:build job will build image and push it to private registry
- CI:deploy job will connect to docker server over ssh, pull latest changes in my configuration registry (workbench), pulls latest image from registry and update service.
What im missing for now?
Backups. All services are backed up automatically every day to offsite location (BackBlaze B2). But when i want to upgrade service i have to do it manually (for now).
Let’s see closer to my workflow.
Pulling changes from original repository
In my private repository i created meow/production branch from which is image built. When is release new version of application manually pulling changes from repository and creating new branch meow/v.X.Y.Z which i will merge with GitLab later after review. So it looks like follows:
1git clone my-private-repo.git
2cd my-private-repo
3git remote add downstream official-repo.git
4git fetch downstream
5git checkout -b meow/vX.Y.Z vX.Y.Z
6git push origin meow/vX.Y.Z
git checkout -b meow/vX.Y.Z vX.Y.Z this is how you checking out new branch from tag. Next i updating theme for example or making some other changes (like add version suffix so i know i using image from my server) and creating merge request. Next is on GitLab CI.
Building image
Im using pretty standard script, you can see it below
1docker-build:
2 # Use the official docker image.
3 image: docker:latest
4 stage: build
5 services:
6 - docker:dind
7 before_script:
8 - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
9 # Default branch leaves tag empty (= latest tag)
10 # All other branches are tagged with the escaped branch name (commit ref slug)
11 script:
12 - |
13 if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
14 tag=""
15 echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
16 else
17 tag=":$CI_COMMIT_REF_SLUG"
18 echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
19 fi
20 - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
21 - docker push "$CI_REGISTRY_IMAGE${tag}"
22 # Run this job in a branch where a Dockerfile exists
23 rules:
24 - if: $CI_COMMIT_BRANCH
25 exists:
26 - Dockerfile
27 tags:
28 - hetzner
This script will build image from Dockerfile and pushing image to private repository. Next step is deploying to production server.
Deploying to server
This job runs only on default branch. (production branch)
There was needed some configuration. Each eserver has it’s own ssh key with read rights for workbench repository. Next i had to create user on which i running jobs. This user is not in sudo group (so it is cannot run privileged tasks on server), it has disabled password login - only option is connect with ssh key and at last it is in docker group so it can run docker-compose or docker commands.
Ok, after successful build gitlab fire deployment job. Each information for it are stored in Environmental variables. In before_script i configure ssh agent and importing private key for connect to server. (this is standard). Next in script i have to login to my private repository
1ssh $SSH_USER@$SSH_HOST -p $SSH_PORT -o LogLevel=ERROR "docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY"
Login informations are passed from CI by default. When this is done I Checking if i have on server my workbench repository and based on this I clonning it or pulling chcnges.
1- |
2 ssh $SSH_USER@$SSH_HOST -p $SSH_PORT -o LogLevel=ERROR << 'EOF'
3 if [ -d "$HOME/workbench" ]; then
4 cd $HOME/workbench
5 git pull origin main
6 else
7 git clone ssh://git@git.<my-server-url-redacted>/workbench.git $HOME/workbench
8 fi
9 EOF
After this i just run taks which is needed for deployment (pulliing docker, backup database - in progress, not done yet, and more). As example here is my homer dashboard deployment script:
1deploy-to-homeserver:
2 stage: deploy
3 variables:
4 GIT_STRATEGY: none
5 image: glcr.themeow.cloud/maymeow/toolkit:latest
6 before_script:
7 - mkdir -p ~/.ssh
8 - eval $(ssh-agent -s)
9 - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
10 - ssh-add <(echo "$SSH_PRIVATE_KEY" | tr -d '\r')
11 script:
12 - ssh $SSH_USER@$SSH_HOST -p $SSH_PORT -o LogLevel=ERROR "docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY"
13 - |
14 ssh $SSH_USER@$SSH_HOST -p $SSH_PORT -o LogLevel=ERROR << 'EOF'
15 if [ -d "$HOME/workbench" ]; then
16 cd $HOME/workbench
17 git pull origin main
18 else
19 git clone ssh://git@git.<my-server-url-redacted>/workbench.git $HOME/workbench
20 fi
21 EOF
22 - ssh $SSH_USER@$SSH_HOST -p $SSH_PORT -o LogLevel=ERROR 'cd $HOME/workbench && docker-compose -f applications/homer/docker-compose.yml pull'
23 - ssh $SSH_USER@$SSH_HOST -p $SSH_PORT -o LogLevel=ERROR 'cd $HOME/workbench && docker-compose -f applications/homer/docker-compose.yml down && docker-compose -f applications/homer/docker-compose.yml up -d'
24 tags:
25 - hetzner
26 rules:
27 - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
So thats all. Thank you for reading.