The problem
Deploys ran through Jenkins, outside the repository, and anyone with push access could change the main branch directly. Nothing in the repository described how a change reached production, and there was no written way back if a release went wrong.
What Nuno did
Nuno split the work into three GitHub Actions workflows: one for checks on every change, one for deploys and one for releases. Each branch maps to one environment. The development, staging and test environments deploy automatically, and production waits for a manual approval. Every production release gets a timestamped tag, so it is always clear what is live. The workflow files are protected by code owners, and pull requests use a template. The deploy key lives in repository secrets and is loaded through an SSH agent at run time. The build also rewrites the Composer web root to the directory layout the hosting platform expects, which was the one step that broke on the first try. Deploy results post to the team chat.
Handover
Nuno wrote a runbook covering the branch model, approvals, release tags and a manual rollback, and revised it three times in July 2025 as the team used it. The goal was that releases would not depend on Nuno being online.
Results
From July 2025, changes reached production through a pull request, an automatic deploy to the test environments and an approved production step, with a release tag that shows what is live. The team got a runbook for releases and rollback. Deploy times and failure rates were not measured before the move, so there are no before and after numbers here.
Solutions
Pipeline
- Three workflows for checks, deploys and releases.
- Automatic deploys to development, staging and test, with manual approval for production.
- Timestamped release tags for every production deploy.
Guardrails
- Code owners on the workflow files and a pull request template.
- Deploy key kept in repository secrets, never in the code.
- Written rollback procedure and runbook.
















