Devops
Table of Contents
- Introduction
- Environments
- Deployment architecture
- Avenirs Workflows
- Self-hosted runners
- GitHub Actions deployments
- Tagging strategy
- Secrets and access
Introduction⇧
This page centralizes everything related to DevOps: environments, runners, and how we deploy through GitHub Actions. The objective is to keep environment parity, minimize manual steps, and make deployments reproducible.
Environments⇧
We operate three environments with the same architecture:
- srv-dev (development)
- srv-qualif (qualification)
- srv-recette (recette)
Common traits:
- SSH access is only allowed over VPN (OpenVPN). The
.ovpnclient configuration is stored in Vaultwarden. - You need an SSH keypair (ed25519) to access the servers (public key stored on server).
- All servers share the same base path under:
/opt/avenirs-dev/
Deployment architecture⇧
Deployment repository
Deployments are orchestrated by the repository:
On each server, this repository is present at:
/opt/avenirs-dev/avenirs-deployment
Key elements:
conf/deployment.conf— central configuration that selects branches/tags to deploy per repository.conf/tags.conf— used by the CI/CD pipeline to determine the target tags for each repository and automatically updatedeployment.confon the target server before triggering the deployment.scripts/— server-side scripts that actually run the deployment sequence.
The main entry points are:
scripts/deploy_srv-dev.shscripts/deploy_srv-qualif.shscripts/deploy_srv-recette.sh
The full step-by-step deployment sequence is described in the
avenirs-deploymentREADME.
Base paths
/opt/avenirs-dev/— base path for deployment artifacts./opt/avenirs-dev/avenirs-deployment/— orchestration scripts + deployment config./opt/avenirs-dev/srv-dev/— environment-specific deployment working copies (repos, compose stacks, etc.).
Avenirs Workflows
The repository avenirs-workflows centralizes reusable workflows and
composite GitHub Actions.
Purpose:
- Avoid duplication
- Standardize CI/CD
- Encapsulate deployment logic
- Accelerate new pipeline creation
Repositories call centralized workflows using:
uses: avenirs-esr/avenirs-workflows/.github/workflows/<workflow>.yaml@main
Repository structure:
avenirs-workflows/
├── .github/
│ ├── actions/
│ └── workflows/
├── gh-page-template/
├── trivy-template/
├── CHANGELOG.md
└── README.md
Benefits:
- Single source of truth
- Clean repositories
- Faster CI/CD evolution
- Safer workflow refactoring
Self-hosted runners⇧
We use a self-hosted GitHub Actions runner on our servers so jobs run inside our network (close to Docker, databases, private services, etc.).
Key principles
- The runner process runs as a local Unix user (we use
$SRV_RUNNER_USER), managed as a systemd service. - For deployments to succeed non-interactively, the runner user must:
- Have no sudo prompts (NOPASSWD for the necessary commands).
- Have the same effective rights as
$SRV_USERon the deployment tree (or share a common group). - Belong to the
dockergroup (or run Docker viasudoNOPASSWD). - Optionally, be part of a shared
deploygroup that owns/opt/avenirs-dev(group write,setgid).
Installation
Variables you will use below:
token— the registration token from GitHub (Org → Settings → Actions → Runners → “New self-hosted runner”).name— runner name (e.g.srv-dev).UNIT— resolved systemd unit name (see command below).REG_TOKEN— removal token (new token generated for removal if needed).
# As root (or a sudo-capable user)
sudo useradd --system --create-home --home-dir /opt/$SRV_RUNNER_USER --shell /bin/bash $SRV_RUNNER_USER 2>/dev/null || sudo useradd -m -d /opt/$SRV_RUNNER_USER -s /bin/bash $SRV_RUNNER_USER
sudo mkdir -p /opt/$SRV_RUNNER_USER
sudo chown -R $SRV_RUNNER_USER:$SRV_RUNNER_USER /opt/$SRV_RUNNER_USER
# Optional (recommanded): shared group ownership for the base path
sudo groupadd -f deploy
sudo usermod -aG deploy $SRV_RUNNER_USER
sudo usermod -aG deploy $SRV_USER
sudo chgrp -R deploy /opt/avenirs-dev
sudo chmod -R g+rwX /opt/avenirs-dev
sudo find /opt/avenirs-dev -type d -exec chmod g+s {} \;
# Allow $SRV_RUNNER_USER passwordless sudo for everything (use with caution)
sudo bash -c 'cat >/etc/sudoers.d/99-$SRV_RUNNER_USER-all <<EOF
Defaults:$SRV_RUNNER_USER !requiretty
Defaults:$SRV_RUNNER_USER env_keep += "JASYPT_ENCRYPTOR_PASSWORD JAVA_HOME M2_HOME NVM_DIR SDKMAN_DIR NODE_OPTIONS"
$SRV_RUNNER_USER ALL=(ALL) NOPASSWD:ALL
EOF'
sudo chmod 0440 /etc/sudoers.d/99-$SRV_RUNNER_USER-all
# Docker access (either add to group or use sudo for docker)
sudo usermod -aG docker $SRV_RUNNER_USER
# Switch to runner user and configure
sudo -iu $SRV_RUNNER_USER
mkdir -p ~/actions-runner && cd ~/actions-runner
# Before running following command, go on github, create new self-hosted runner (select the correct runner image) and execute download parts commands (except the first mkdir)
./config.sh --url https://github.com/avenirs-esr --token "$token" --name "$name" --runnergroup Default --work _work --labels self-hosted,Linux,X64,"$name"
./run.sh
# Ctrl+C to stop foreground runner
Environment variable (already in $SRV_RUNNER_USER):
# Install all dependencies that $SRV_RUNNER_USER will need (Java, Maven, Node).
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 21.0.4-oracle
sdk default java 21.0.4-oracle
sdk install maven 3.9.9
sdk default maven 3.9.9
export NVM_DIR="$HOME/.nvm"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
. "$NVM_DIR/nvm.sh"
nvm install --lts
nvm alias default "lts/*"
echo "export JASYPT_ENCRYPTOR_PASSWORD='your-secret-value'" >> ~/.bashrc
Git configuration (already in $SRV_RUNNER_USER):
git config --global --add safe.directory /opt/avenirs-dev/avenirs-deployment
git config --global --add safe.directory /opt/avenirs-dev/srv-dev
git config --global --add safe.directory /opt/avenirs-dev/srv-dev/services/apisix/apisix-docker
git config --global --add safe.directory /opt/avenirs-dev/srv-dev/services/apisix/apisix-dashboard
git config --global --add safe.directory /opt/avenirs-dev/srv-dev/services/avenirs-portfolio/avenirs-portfolio-security
git config --global --add safe.directory /opt/avenirs-dev/srv-dev/services/avenirs-portfolio/avenirs-cofolio-client
git config --global --add safe.directory /opt/avenirs-dev/srv-dev/services/avenirs-portfolio/avenirs-cofolio-api
git config --global --add safe.directory /opt/avenirs-dev/srv-dev/services/cas/cas-overlay-template
exit
Install as a service and start:
cd /opt/$SRV_RUNNER_USER/actions-runner
sudo ./svc.sh install $SRV_RUNNER_USER
sudo ./svc.sh start
# Locate and enable the unit
systemctl list-unit-files --type=service | grep '^actions\.runner'
# Example export (adjust if needed):
# export UNIT="actions.runner.***.***.service"
sudo systemctl enable "$UNIT"
systemctl status "$UNIT"
Remove / Unregister
# Stop & uninstall service
cd /opt/$SRV_RUNNER_USER/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh uninstall
sudo systemctl daemon-reload
# Remove runner registration (as $SRV_RUNNER_USER)
sudo -iu $SRV_RUNNER_USER
cd /opt/$SRV_RUNNER_USER/actions-runner
./config.sh remove --token "$REG_TOKEN"
Troubleshooting
- Docker socket: “Permission denied” → add
$SRV_RUNNER_USERtodockerand restart the service.- Git dubious ownership → ensure files under
/opt/avenirs-devare owned by$SRV_RUNNER_USER:deploy(if a deploy group axist), avoid root-owned files.- All tools not found → ensure environment variables are preserved when using
sudo(e.g.JAVA_HOME,M2_HOME,NVM_DIR,SDKMAN_DIR, etc.).
GitHub Actions deployments⇧
Reusable workflow
Deployments are driven by a shared reusable workflow (called from repositories via uses:).
Typical characteristics:
- Centralizes the “how to deploy”
- Keeps repos light (only event wiring + inputs)
- Runs on the correct self-hosted runner (srv-dev / srv-qualif / srv-recette)
- Delegates the actual deployment to
avenirs-deploymentserver scripts
Dev deployments
Development is deployed automatically.
Trigger condition:
- Every time a Pull Request is merged into a
developbranch - Only for repositories that have a direct impact on the application (API, back-office, client, etc.)
Process:
- A GitHub Actions workflow is triggered on merge to
develop - The deployment job runs on the srv-dev self-hosted runner
- The server-side deployment script from
avenirs-deploymentis executed - Docker images and containers are refreshed accordingly
This ensures that srv-dev always reflects the latest validated develop state.
Qualif deployments
Qualification follows a controlled continuous deployment process driven by User Stories (US).
1. Functional readiness
When all sub-tasks of a User Story are merged into develop:
- A workflow automatically detects that the US is complete
- Check if the US have a “[TEST]” sub-issue
- The US is moved to the “In Review” column in GitHub Projects
This column represents:
“Ready for qualification deployment”
If the US dont have a “[TEST]” sub-issue, a comment is automatically added to the US to notify the team that it cannot be qualified until test cases are defined, and the US still in “In Progress” column.
When a sub-issue is added to a US that has been completed, regardless of which column the US is in, it is automatically moved to “Backlog Ready” column
2. Nightly deployment workflow
Every night, a scheduled workflow runs.
Step 1 — Check pending US
- The workflow checks if there are User Stories in the “In Review” column.
- If none → the workflow stops.
- If some exist → the workflow continues.
Step 2 — E2E validation on develop
- End-to-end tests are executed against the current
developstate. - If tests fail:
- A comment is automatically added on each US in “In Review”
- Deployment is aborted.
Step 3 — Deployment to qualif
If E2E tests pass:
- The deployment workflow runs on srv-qualif
- The deployment is executed via
avenirs-deployment
If deployment fails:
- A comment is automatically added on the affected US.
Step 4 — Move to Recette
If deployment succeeds:
- All User Stories that were in “In Review”
- Are automatically moved to the “Recette” column
This guarantees:
- No deployment without validated E2E tests
- Atomic qualification batches
- Automatic traceability on failure
Recette deployments
Recette deployments can be:
- Manual
- Or automatically triggered by a release
Automatic deployment
Triggered when:
- A new GitHub Release is created on
srv-devrepository - The tag contains
-rc(e.g.1.2.3-rc)
Process:
- The deployment workflow reads
conf/tags.conffrom theavenirs-deploymentrepository. tags.confdefines the exact tag to use for each service.- The workflow updates the server-side
deployment.confaccordingly. - The deployment command is executed on srv-recette.
- Docker images are updated and containers restarted.
This ensures:
- Full control over service versions
- Cross-service version consistency
Tagging strategy⇧
We use Semantic Versioning (SemVer) as our versioning strategy:
MAJOR.MINOR.PATCH
Version increment rules:
-
MAJOR — significant milestone or public version step
(e.g. initial V0 release, future V1, V2, etc.) -
MINOR — sprint release version
Represents a functional increment delivered at the end of a sprint. -
PATCH — bug fixes or small corrective updates
Backward-compatible fixes applied on top of a sprint version.
Environment-specific tags
Based on SemVer, we use the following conventions:
-
Qualif:
X.Y.Z-qa
GitHub Release marked as pre-release -
Recette:
X.Y.Z-rc
GitHub Release marked as pre-release -
Production:
X.Y.Z
Final GitHub Release (not pre-release)
Examples:
1.4.2-qa1.4.2-rc1.4.2
Secrets and access⇧
Secrets are stored in:
- GitHub Secrets (workflow execution)
- Vaultwarden (server access, VPN, SSH keys, deployment credentials)
Commonly used secrets/variables:
SRV_USERSRV_RUNNER_USERSRV_BASE_PATHSRV_DEPLOY_CMD/SRV_UPDATE_DEPLOYMENT_CMD(if used by the deployment composite/action)JASYPT_ENCRYPTOR_PASSWORD- VPN
.ovpnfile and SSH keys