Martin Maslyankov deaf9a9890 feat: add environment-based mutable tagging to CI templates
build-push.yaml:
- Add environment-tag and version-tag optional inputs
- Auto-detect environment from branch: main/master→:prod, staging→:staging, dev→:dev
- Replace :latest with :prod for main branch
- Support manual version tags for node-specific testing

deploy-k8s.yaml:
- Switch from image tag sed to deploy-timestamp annotation bump
- Mutable tags (:prod/:staging) stay constant in manifests
- ArgoCD detects rollout via timestamp annotation change
- Preserves SHA in commit message for traceability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 00:52:15 +03:00

wectrl CI Pipeline Templates

Reusable Gitea Actions workflows for all wectrl services. These live in the wectrl-net/ci-templates repository and are called from each service repo.

Setup

1. Create the ci-templates repo on Gitea

Create a new repo at git.wectrl.net/wectrl-net/ci-templates and push the .gitea/workflows/ directory from this template.

2. Required secrets per service repo

Each service repo needs these secrets configured in Gitea (Settings > Actions > Secrets):

Secret Description
REGISTRY_USER Gitea username for pushing images
REGISTRY_TOKEN Gitea token with packages:write scope
GIT_USER Gitea username for pushing to k8s cluster repo
GIT_TOKEN Gitea token with repo:write scope on wectrl-k8s-cluster

3. Add workflows to your service repo

Copy the appropriate example from examples/ into your repo's .gitea/workflows/ directory and customize the parameters.

Available Templates

Lint workflows

Template Language Tool
lint-python.yaml Python Ruff
lint-node.yaml Node/TS ESLint + tsc
lint-rust.yaml Rust Clippy + rustfmt

Test workflows

Template Language Tool
test-python.yaml Python pytest
test-node.yaml Node/TS npm test (Vitest/Jest)
test-rust.yaml Rust cargo test

Build & Deploy workflows

Template Purpose
build-push.yaml Build Docker image, push to git.wectrl.net registry
deploy-k8s.yaml Update image tag in wectrl-k8s-cluster repo (ArgoCD GitOps)

Examples

Python + React fullstack (h1per-pms pattern)

# .gitea/workflows/ci.yml
name: CI
on:
  pull_request:
    branches: [main, dev]
  push:
    branches: [main, dev]

jobs:
  lint-backend:
    uses: wectrl-net/ci-templates/.gitea/workflows/lint-python.yaml@main
  lint-frontend:
    uses: wectrl-net/ci-templates/.gitea/workflows/lint-node.yaml@main
    with:
      working-directory: web
  test-backend:
    uses: wectrl-net/ci-templates/.gitea/workflows/test-python.yaml@main
  test-frontend:
    uses: wectrl-net/ci-templates/.gitea/workflows/test-node.yaml@main
    with:
      working-directory: web

Rust service (wectrl-telemetry pattern)

# .gitea/workflows/ci.yml
name: CI
on:
  pull_request:
    branches: [main, dev]
  push:
    branches: [main, dev]

jobs:
  lint:
    uses: wectrl-net/ci-templates/.gitea/workflows/lint-rust.yaml@main
  test:
    uses: wectrl-net/ci-templates/.gitea/workflows/test-rust.yaml@main

Pipeline flow

PR / push to dev     push to main
      │                    │
      ▼                    ▼
  ┌───────┐          ┌───────┐
  │  Lint  │          │  Lint  │
  │  Test  │          │  Test  │
  └───────┘          └───┬───┘
                         │
                         ▼
                   ┌───────────┐
                   │ Build &   │
                   │ Push Image│
                   └─────┬─────┘
                         │
                         ▼
                   ┌───────────┐
                   │ Update    │
                   │ k8s repo  │
                   └─────┬─────┘
                         │
                         ▼
                   ┌───────────┐
                   │ ArgoCD    │
                   │ auto-sync │
                   └───────────┘

Service mapping

Service Repo Stack Deploy path in k8s-cluster
h1per-pms wectrl-net/h1per-pms Python + React/TS saas/h1per/backend/deployment.yaml
clok1-landing wectrl-net/clok1-landing Node/TS saas/clok1/app/deployment.yaml
solar-platform wectrl-net/solar-platform TBD platform/components/wectrl-solar-platform/api-deployment.yaml
solar-web wectrl-net/solar-web TBD platform/components/wectrl-solar-platform/web-deployment.yaml
client-portal API wectrl-net/wectrl-client-portal TBD platform/components/wectrl-client-portal/api-deployment.yaml
client-portal frontend wectrl-net/wectrl-client-portal-frontend TBD platform/components/wectrl-client-portal/frontend-deployment.yaml
wectrl-telemetry wectrl-net/wectrl-telemetry Rust TBD (needs k8s manifests)

Customization

All templates accept inputs with sensible defaults. Override only what differs from the standard:

jobs:
  lint:
    uses: wectrl-net/ci-templates/.gitea/workflows/lint-python.yaml@main
    with:
      python-version: "3.12"        # override default 3.13
      working-directory: backend     # if Python code is in a subdirectory

ARM64 builds and QEMU emulation

Our Hetzner cluster runs on ARM64 (CAX) nodes, so all Docker images target linux/arm64 by default. Since our Gitea runners are amd64, ARM64 images are built via QEMU user-mode emulation through Docker Buildx.

Trade-offs:

QEMU emulation (default) Native ARM64 runner
Setup Zero — works on any amd64 runner Requires provisioning a self-hosted ARM64 runner
Build speed 3-10x slower for compiled languages (Rust, Go, C) Native speed
Reliability Occasional QEMU edge cases with complex syscalls No emulation issues
Best for Node/Python/lightweight builds Rust services, heavy native compilation

For Rust services (e.g. wectrl-telemetry): QEMU emulation of ARM64 Rust compilation is significantly slower. When build times exceed ~10 minutes, switch to a native ARM64 runner:

jobs:
  build:
    uses: wectrl-net/ci-templates/.gitea/workflows/build-push.yaml@main
    with:
      image-name: git.wectrl.net/wectrl-net/wectrl-telemetry
      runner: self-hosted-arm64   # bypass QEMU, use native ARM64 runner
    secrets:
      REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
      REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}

The runner input on build-push.yaml defaults to ubuntu-latest (amd64 + QEMU). Set it to your ARM64 runner label when needed.

Notes

  • All workflows trigger on both main and dev branches (per CON-569 branching strategy)
  • Build & deploy only runs on push to main (production deploy)
  • Dev/staging deploys can be added by extending deploy-k8s.yaml with a branch condition
  • The default platform is ARM64 (linux/arm64) matching the Hetzner CAX cluster nodes — see ARM64 builds above
  • Semantic versioning tags (v1.2.3) are supported by build-push.yaml via the metadata action
  • Container images used by CI (e.g. catthehacker/ubuntu) are pinned by digest to prevent supply-chain drift
Description
Reusable CI/CD workflow templates
Readme 66 KiB