Files
cesnimda ae8e6b672e
CI / backend (push) Successful in 1m14s
CI / frontend (push) Successful in 28s
Security / secrets (push) Successful in 6s
Security / dependencies (push) Successful in 1m14s
Deploy Staging / deploy (push) Failing after 43s
Git workflow, environments & CI/CD pipeline (#1)
2026-07-01 11:44:34 +02:00

7.5 KiB
Raw Permalink Blame History

InboxIntel — Git Workflow & Environment Strategy

The single source of truth for how we branch, commit, version, and promote code across environments. Optimised for a solo developer on Windows 11 with a future Linux production server. Kept deliberately lightweight — every rule here earns its place.


1. Branch strategy

A trimmed GitHub Flow + a long-lived develop model. Two permanent branches, short-lived branches off develop.

Branch Lives for Purpose Deploys to
main forever Always releasable. Every commit is tagged & shippable production
develop forever Integration branch. What staging runs staging
feature/* hoursdays One feature or refactor dev (local)
fix/* hours Non-urgent bug fix dev (local)
hotfix/* minuteshrs Urgent prod fix, branched from main prod (fast)
release/x.y.0 optional Only if a release needs stabilisation before tagging staging

Why this shape (not full GitFlow): a solo dev doesn't need GitFlow's ceremony (separate release managers, parallel release trains). But keeping develop separate from main gives one thing that pure trunk-based can't: a staging environment that always mirrors develop while main stays clean and tag-perfect for production. release/* exists only when you want to freeze features and stabilise — skip it for routine work.

Normal flow

main ──────●────────────────────●──────────  (tagged: v1.2.0, v1.3.0)
            \                   /
develop ──●──●──●──●──●──●──●──●────────────  (staging)
              \   /   \       /
feature/x      ●─●     \     /
fix/y                   ●───●
  1. git switch develop && git pull
  2. git switch -c feature/sender-policy
  3. Commit in small conventional commits.
  4. Push, open a PR into develop (Gitea). CI must be green.
  5. Squash-merge. Delete the branch.
  6. When develop is ready to ship → PR develop → main, tag, deploy.

2. Commit conventions — Conventional Commits

Format: type(scope): short imperative summary

Types: feat, fix, docs, refactor, test, chore, ci, perf, build, style, security.

Rules:

  • Summary ≤ 72 chars, imperative mood ("add", not "added").
  • One logical change per commit.
  • Body explains why, not what (the diff shows what).
  • Breaking change → add ! (feat!:) and a BREAKING CHANGE: footer.

Examples (matching this repo's history):

feat(ui): F1+F2 — component primitives + rebuilt app shell
fix(security): systemic IDOR safeguard via EF global query filters
ci: add Gitea Actions build + test pipeline

Why: conventional commits drive automatic semver bumps and a generated CHANGELOG, and make git log scannable. feat → minor, fix → patch, BREAKING CHANGE → major.


3. Pull request / merge flow (Gitea)

Even solo, PRs are worth it: they run CI, give a diff review checkpoint, and build a paper trail.

  • Target: feature/* and fix/*develop. develop/hotfix/*main.
  • Gate: the CI workflow (backend build+test, frontend build) must pass.
  • Merge style: squash-merge feature branches (one clean commit on develop). Merge commit for develop → main (preserves the integration history and makes the release boundary visible).
  • Branch protection (Gitea → Settings → Branches): protect main and develop; require status checks to pass; disallow force-push.

4. Versioning — Semantic Versioning (MAJOR.MINOR.PATCH)

  • MAJOR — breaking API/behaviour change.
  • MINOR — backward-compatible feature.
  • PATCH — backward-compatible fix.

The current version lives in VERSION and is the one place bumped per release. Pre-1.0 while scaffolding: stay on 0.x (minor = features, patch = fixes; anything may change).


5. Tagging & releases

Tags are cut only on main, annotated, prefixed v:

git switch main && git pull
# bump VERSION + CHANGELOG in a release commit, then:
git tag -a v1.3.0 -m "v1.3.0 — sender policy + staging overlay"
git push origin main --tags
  • Tag == the exact commit deployed to production == the rollback target.
  • The tag message summarises the release; details live in CHANGELOG.md.

6. Hotfix process

For a bug already in production:

git switch main && git pull
git switch -c hotfix/oauth-callback-500
# fix + test
git commit -m "fix(security): guard null OAuth state on callback"
# PR hotfix/* -> main, CI green, merge
git switch main && git pull
git tag -a v1.3.1 -m "v1.3.1 hotfix — OAuth callback" && git push origin main --tags
# deploy the tag, THEN back-merge so develop doesn't lose the fix:
git switch develop && git merge main && git push

The back-merge to develop is the step people forget — without it the next release silently reintroduces the bug.


7. Environments

Env Host Runtime Config source Purpose
Development Windows 11 (native) dotnet run + vite (hot reload) appsettings.Development.json + user-secrets fast iteration, debugging
Staging Windows 11 (Docker) Linux containers, ASPNETCORE_ENVIRONMENT=Production deploy/.env.staging + docker-compose.staging.yml production rehearsal
Production Linux server (Docker) identical Linux containers deploy/.env on the server live

Parity principle: dev is fast (native, Windows) and not production-shaped — that's fine, it's for inner-loop speed. Staging is the parity gate: it runs the same Linux images Docker builds for production, so "works in staging" genuinely predicts "works in prod". The only staging↔prod differences are the dev banner, capped sync, ports, and volume namespace — see docker-compose.staging.yml.

Commands

# DEV (native, hot reload) — two terminals
dotnet run --project src/InboxIntel.Api        # http://localhost:5080
cd frontend; npm run dev                        # http://localhost:5173

# STAGING (production-shaped, on Windows via Docker)
./deploy/up.ps1 -Staging                         # http://localhost:18081
./deploy/down.ps1 -Staging

# PRODUCTION-shaped run locally (smoke test the real config)
./deploy/up.ps1                                  # http://localhost:8081

Dev and staging use different ports and different volumes, so they can run at the same time and never share a database.


8. Git hooks

Installed via core.hooksPath (run once per clone):

./scripts/install-hooks.ps1
  • pre-commit (fast): blocks committed .env/secrets, verifies dotnet format.
  • pre-push (thorough): dotnet build -c Release + dotnet test + frontend build — the same gate CI runs, caught before the push.

Emergency bypass: --no-verify. CI still enforces the gate server-side, so a bypassed push can still be rejected by branch protection.