An agency with five developers and forty client repos has no one whose job is the tooling. Yet it rebuilds the same build and deploy setup with every new client. Its client apps sit at different ages. That is why an agency needs a platform more than many product companies do: Every new client app is another chance to reinvent how code gets built and deployed.

The CNCF Platforms White Paper gives small teams a useful permission slip. It advises building “the thinnest viable platform layer over implementations from managed providers,” and it notes that a very simple platform could be a wiki page with links to standard operating procedures. For an agency, the thin layer can start as a project template and a pipeline that every repository calls. Environment rules come next. The examples below use GitHub and Copier because many agencies already work there; the same three pieces exist on other CI systems.

Write the Page Before the Template

Start with the wiki page, because it forces decisions that tooling would otherwise hide. Where does a staging site live, and who can see it? How does a developer get access to a client’s hosting, and how is that access removed when the engagement ends? If the answers fit on one page, the platform has a scope. If they don’t, the agency has a policy problem, and no template will solve it.

One Template, Versioned Like Code

GitHub’s template repositories copy the directory structure and files of the default branch into a new repository. The catch is that the copy happens only once. GitHub’s documentation says branches created from a template have unrelated histories, so you cannot open pull requests or merge between them. When the template later gains a better lint setup, or a security header the agency now wants on every site, the older client repos never hear about it.

A template that keeps paying off needs an update path. Copier provides one. If the template includes Copier’s answers-file template, each generated project gets a .copier-answers.yml file. When you run copier update, Copier compares the template’s Git tags, checks out the latest one and applies the changes. Conflicts are left as markers in the files for a person to review before committing. The template now has releases. The answers file records the template commit the project was last updated from (its _commit key, per Copier’s guide to creating a template), so moving a client repo forward is an ordinary pull request.

Keep the template boring. It should hold only what every client app shares: the directory layout, lint and formatting config, a README skeleton with the same headings every time, and the small file that calls the shared pipeline. Anything specific to one client belongs in that client’s repo.

One Pipeline, Called From Everywhere

CI files are where copy and paste does the most damage. A fix made in one repo’s workflow never reaches the others.

In GitHub Actions, a workflow becomes reusable when its on key includes workflow_call, and a client repo calls it at the job level with uses: {owner}/{repo}/.github/workflows/{filename}@{ref}. The ref can be a branch, a release tag or a commit SHA. GitHub’s documentation on reusing workflows calls the SHA the safest option for stability and security.

That leaves a choice an agency should make on purpose. Pin every client repo to a tag or SHA, and a change to the shared pipeline cannot break a client build on the day it lands, but someone has to move each repo forward. Point at a branch, and fixes spread instantly, mistakes included. A middle ground that holds up is to tag pipeline releases, pin client repos to tags, and bump them in the same pull requests that bring template updates.

Secrets do not pass through on their own; the caller hands them over explicitly, or uses secrets: inherit, which works only when the caller and shared workflow sit in the same GitHub organization or enterprise. Agencies whose client repositories live in separate GitHub organizations also need to check access restrictions before relying on private shared workflows. Nesting is capped: A chain of reusable workflows can run ten levels deep, counting the top caller. A small team should stay far below that.

Environments That Mean the Same Thing Everywhere

“Staging” tells you nothing if it means something different on each client project. Pick the names once and let the CI system enforce what each name implies.

In GitHub, an environment can carry deployment protection rules. You can restrict which branches or tags may deploy. On plans and repositories that support required reviewers, you can name reviewers, and only one of them has to approve before the job proceeds. Environment secrets are available only to jobs that use that environment, and only after its applicable rules pass. Where required reviewers are supported, a production database password can sit behind an approval without anyone writing a custom gate.

Check the plan before you rely on this. GitHub’s docs say users on GitHub Free can configure environments only for public repositories. Most agency client work sits in private ones. GitHub Team and GitHub Pro accounts can use environments, environment secrets and deployment branch restrictions on private repositories, but required reviewers are available only for public repositories on those plans. Private repositories need an eligible GitHub Enterprise plan to use that approval mechanism.

Config Stays Out of the Repo

The Twelve-Factor App defines config as everything likely to vary between deploys and says to keep it in environment variables, out of tracked files. Its test works well in an agency code review: Could this repository be made public right now without exposing a credential? Client repos also change hands when a contract ends, and a repo that fails the test turns a handover into an afternoon of rotating keys.

What to Leave Out

The white paper also says platform teams must treat their platforms like products and develop them together with users. In an agency, the users are usually the same few developers who maintain the platform, which makes it tempting to keep adding to it. A self-service portal or a custom CLI is a second product with no paying client behind it. Until the template and the shared pipeline stop changing every month, a portal would have nothing stable to sit on.


About the author: Mugees Ahmed is with DistroCode, which has built websites for clients since 2017 and builds custom web apps in Laravel, React and Vue. https://distrocode.com

SHARE THIS STORY