6. First repo and pipeline
What it is
The first backend service, created from our template, running on your laptop, and deployed to the dev environment by the pipeline when you push a git tag. After this step, nobody deploys by hand again.
Why we do it
- A pipeline from day one means every change reaches dev the same way, and prod later.
- Starting from the template gives you the Code organisation layout, tests, Dockerfile and deploy files without copying another product.
Before you start
- Steps 3, 4 and 5 done: the repo
acme-apiexists, you can sign in to the dev account, anddev.example.comresolves. - Docker Desktop, Node (the version in the template's
.nvmrc), Terraform, AWS CLI. - The current template repo name, from the tech lead.
How it fits together
The detail of each box is in Release pipeline.
Steps
1. Create the service from the template
gh repo create example-co/acme-api --private --template <template-org>/<api-template>
gh repo clone example-co/acme-api && cd acme-api
2. Rename the template's CI identity
The template uses placeholder names. Replace every one with this service's names:
| Placeholder | Becomes | Found in |
|---|---|---|
template-api | acme-api | package.json, README.md |
| ECR repository | acme-api | buildspec.yml |
| Container name | api | appspec.yml, deploy/*/taskdef.json |
| Secret prefix | /acme/<env>/api/ | deploy/*/taskdef.json |
| Log group | /ecs/acme-dev-api | deploy/dev/taskdef.json |
grep -rn "template" --exclude-dir=node_modules . # must find nothing
3. Run it on your laptop
cp .env.example .env
docker compose up -d # Postgres and Redis
npm ci
npm run migrate
npm run dev
curl localhost:3000/health # {"status":"ok"}
4. Create the dev infrastructure
In acme-infra, the dev environment uses our Terraform modules for: the ECR repository,
ECS cluster and service, load balancer with a target group checking /health, the
database, SSM placeholders for secrets, the GitHub connection and the pipeline.
cd acme-infra/envs/dev
aws sso login --profile acme-dev
terraform init
terraform plan -out plan.tfplan
terraform apply plan.tfplan
5. Finish the GitHub connection by hand
Terraform creates the connection to GitHub as Pending. A person must finish it once:
Developer Tools, Settings, Connections, select it, Update pending
connection, and install the AWS app on the example-co organisation.
6. Fill the secrets
Terraform created the parameters with placeholder values. Set the real ones:
aws ssm put-parameter --profile acme-dev --overwrite --type SecureString \
--name /acme/dev/api/JWT_SECRET --value "$(openssl rand -base64 48)"
Rules for secrets are in Secrets and config.
7. Deploy by tag
git switch dev
git tag dev-v-0.1.0
git push origin dev-v-0.1.0
Watch it in CodePipeline. Build takes a few minutes; CodeDeploy then starts the new
version next to the old and moves traffic when /health passes.
Check it worked
curl https://api.dev.example.com/health # {"status":"ok"}
aws ecs describe-services --profile acme-dev --cluster acme-dev \
--services api --query 'services[0].deployments[].rolloutState'
If it goes wrong
| You see | It means |
|---|---|
| Pipeline fails at Source with an access error | The GitHub connection is still Pending. Step 5 |
| Build fails with "Bad substitution" | buildspec.yml uses a bash-only feature; CodeBuild runs dash |
| Task fails with "unable to assume role" | A <PLACEHOLDER> is left in taskdef.json. Step 2 |
| Task stops with a secrets error | The execution role cannot read /acme/dev/api/* or decrypt it |
| Health check fails but the app runs | It checks / and gets a redirect. Point it at /health |
| A merge did not deploy | Merges never deploy. Push a tag |
Rules
Every service has /health from the first commit, and the load balancer checks it.
Only the dev pipeline exists until dev has deployed cleanly. Then add staging and prod the same way.
Mistakes we made
- Two web apps were forked from a live one and kept its CI names. A dev tag on either would have deployed over the live app.
- Merged code sat undeployed for hours while people suspected broken webhooks. The tag was missing.
Done when
- Service created from the template; no template names left.
- Runs locally with
/healthreturning 200. - Dev infrastructure applied; GitHub connection Available.
- Secrets filled in SSM.
-
dev-v-0.1.0deployed;https://api.dev.example.com/healthreturns 200.