Starting a new client project involved a checklist of manual steps, repo creation, CI setup, environment variables, base dependencies, that reliably ate the better part of a day and was easy to get slightly wrong. Someone would inevitably forget to add a required secret to the new repo, or copy a stale template that was two versions behind our current standard, and the mistake wouldn't surface until a deploy failed days later. We built an internal CLI to automate nearly all of it, and it's quietly become one of the most-used pieces of internal tooling we've ever shipped.
What the checklist actually looked like
Before the CLI existed, spinning up a new project meant working through a shared onboarding document, manually creating a GitHub repository from one of several templates, copying over CI configuration by hand, provisioning cloud resources through a web console, and setting up environment variables one at a time by cross-referencing a spreadsheet of standard values. Every step was individually simple, but the sheer number of steps meant a fair amount of friction and an uncomfortably high rate of small mistakes, and the process differed slightly depending on which template you started from, which meant the document itself kept falling out of date.
We tracked how long a typical new-project setup actually took over a few months before building the CLI, partly to build the case for investing engineering time in tooling rather than another feature, and partly out of genuine curiosity about where the time was going. The answer was that no single step took very long, but the total, including the back-and-forth when someone got a step wrong and had to redo it, consistently landed between four and seven hours.
What the CLI does
The CLI scaffolds a new repo from one of a few standardized templates, wires up CI using our shared reusable workflows, and provisions initial cloud resources in a single command. Running it prompts for a project name, a template choice, and a small number of client-specific configuration values, then handles repo creation, initial commit, CI wiring, secret provisioning, and a base set of cloud resources without further manual intervention. The time from "let's start a new project" to a deployed hello-world page dropped from most of a day to well under fifteen minutes.
- The CLI scaffolds a new repo from one of a few standardized templates, wires up CI using our shared reusable workflows, and provisions initial cloud resources in a single command.
- The time from "let's start a new project" to a deployed hello-world page dropped from most of a day to well under fifteen minutes.
- Secret provisioning happens through the CLI directly rather than a manual copy-paste step, which eliminated an entire category of misconfigured-environment bugs we used to see in the first week of a new project.
Secret provisioning happens through the CLI directly, pulling from a centrally managed vault rather than requiring someone to manually copy values into a new repo's settings, which eliminated an entire category of misconfigured-environment bugs we used to see reliably in the first week of a new project. That category of bug was almost always caught eventually, but usually by whoever was unlucky enough to be debugging a confusing failure in a brand-new project with no other obvious cause.
Building and maintaining the tool
We built the CLI in a way that intentionally mirrors the manual steps it replaces rather than introducing new abstractions, mostly so anyone on the team can read its source and understand exactly what it does without needing internal documentation. It's a fairly thin wrapper around the GitHub API, our cloud provider's API, and the secrets vault's API, with the actual logic amounting to careful sequencing and error handling rather than anything conceptually novel. That simplicity has mattered more than we expected; when the CLI needs updating because a template changes or a new cloud resource type gets added, the change is usually small and low-risk precisely because the tool isn't hiding complexity behind clever indirection.
Maintaining the templates the CLI scaffolds from has become an ongoing, low-key responsibility rather than a one-time build, since a template that's fallen behind the team's current standards defeats much of the tool's purpose. We rotate template maintenance ownership quarterly among the team, mostly to keep any one person from becoming a bottleneck and to make sure the templates reflect current thinking rather than whatever was true when they were first written.
What we'd do differently
Internal tooling investments like this rarely feel urgent enough to prioritize against feature work with a visible deadline, but the CLI has already paid for its build time many times over across the projects started since, somewhere north of thirty new projects at this point. If we were building it again, we'd invest in the template rotation process from the start rather than only introducing it after noticing the first template had quietly drifted out of date for nearly two quarters. We'd also add basic usage analytics sooner; it took us longer than it should have to notice that one of our templates was almost never chosen, which turned out to mean it should have been retired rather than maintained.
Getting buy-in to build it in the first place
Convincing leadership to allocate engineering time to internal tooling, rather than another client-facing feature, took more effort than building the tool itself. We made the case using the time-tracking data from the months we'd spent observing how long manual setup actually took, framed explicitly as a cost multiplied across every future project rather than a one-time inconvenience. That framing landed better than a more abstract argument about developer experience would have, since it gave leadership a concrete number, roughly five hours per project, to weigh against the CLI's estimated two-week build time. The payback period, once we did the arithmetic, was under half a dozen projects, which made the investment an easy approval once someone actually ran the numbers rather than relying on a general sense that manual setup was annoying.
Extending the CLI beyond initial setup
Once the CLI existed for project scaffolding, teams started asking for it to handle other repeated setup tasks too: adding a new environment to an existing project, rotating a credential across every project that uses it, spinning up a temporary preview environment for a client demo. We resisted turning it into a catch-all tool for everything remotely related to project setup, since that path leads to a sprawling, hard-to-maintain tool that tries to do too much. Instead we added a small number of additional commands that clearly extended the original scaffolding use case, while pushing back on requests that would have meant the CLI taking on responsibilities that belonged more naturally in a different tool entirely, like ongoing infrastructure management once a project is already live.
Handling the templates that fell out of use
The usage analytics mentioned above eventually told us more than just which template to retire; it showed a clear pattern of which client project types were actually common versus which templates existed mostly because someone had needed them once. We consolidated two rarely-used templates into a single more configurable one rather than maintaining three templates for what turned out to be minor variations on the same underlying project shape, which reduced the ongoing template maintenance burden without removing any capability teams were actually relying on. The lesson we took from this: build the usage tracking in from day one, since guessing which templates matter from memory turned out to be wrong more often than we expected.
Error handling and the failure modes we designed for
A tool that runs unattended through repo creation, secret provisioning, and cloud resource setup needs to fail safely partway through, since a half-completed run that leaves a project in an inconsistent state is arguably worse than the manual process it replaced. Every step the CLI performs is designed to be safely re-runnable, checking whether a resource already exists before attempting to create it again, so a run that fails at, say, the cloud resource provisioning step can simply be re-invoked after the underlying issue is fixed rather than requiring someone to manually figure out what already succeeded and what didn't. That idempotency took extra design effort up front but has meant a failed run is a minor annoyance rather than a cleanup project.
Measuring adoption after the fact
Beyond the time-savings case we made to get the CLI approved, we've kept tracking its usage since launch, partly to catch template drift as mentioned earlier and partly to understand whether the tool was actually being used as intended across the team rather than by just the handful of engineers who championed it initially. Usage has stayed consistently high across nearly every engineer who's started a new project since launch, with the rare exceptions mostly explained by genuinely unusual project requirements that fall outside what any of the current templates cover well, which we treat as a signal for where the next template might be worth building rather than a failure of adoption.
Onboarding the team onto a new tool
Even a genuinely useful tool doesn't get adopted automatically; we ran a short internal demo when the CLI first shipped and made using it the default recommendation in our project-starting documentation, rather than assuming people would discover it organically. A few engineers who'd developed their own personal scripts for parts of the old manual process were initially reluctant to switch over, understandably, since their scripts already worked fine for their own workflow. Showing those specific engineers how the CLI handled edge cases their personal scripts didn't, secret rotation being the clearest example, won most of them over within the first month without needing to mandate its use.