Continuous delivery
Your reorder app works and its tests pass. Now every change has to reach real shops the same safe way, every time, without anyone typing deploy commands on a laptop. This lesson builds a small pipeline with a staging and a production environment, shows how the API, the database and the mobile app each go back when a release goes wrong, and ends with a release checklist you will use in the next lesson's launch.
By the end you can
- Explain continuous delivery as one pipeline that builds once and promotes the same artifact from staging to production
- Keep staging and production apart with environment variables and per-environment secrets, in the API and in the Expo app
- Write a GitHub Actions workflow that tests, builds a container image, migrates, deploys and smoke-tests
- Plan the way back for each part: redeploy the API, expand-first migrations, and EAS Update rollbacks
- Run a release with a short checklist and clear go and no-go rules
The everyday picture
A bakery chain has a new bread recipe. Nobody sends the recipe card to fifty shops on a Friday night and hopes. First the head baker bakes a test batch in the back kitchen and checks it: weight, colour, taste. Then one shop sells the new loaf for a morning while the team watches what comes back. Only then does the same recipe card, word for word, go to every shop. And every shop keeps the old card in the drawer, so if customers complain, they can go back to it by lunch.
Continuous delivery is that routine for software. The test batch is your automated tests. The single shop is staging. The recipe card that travels unchanged is the build you made once. The old card in the drawer is your way back. And the routine runs for every change, small or large, so it stays boring.
One pipeline, one artifact, two environments
In Testing what matters you wrote unit, API and end-to-end tests, and GitHub Actions: continuous integration showed how to run them on every pull request. That is continuous integration (CI). Continuous delivery (CD) takes the next step: every change that reaches main is built, deployed to a test environment and checked, so that releasing it to users is one small, repeatable action. If even that last step happens automatically, with no person saying go, the name is continuous deployment. For a first app with a handful of shops, a person saying go is the right choice.
The pipeline is a line of stages, and a change only moves forward when the stage before it is green:
- Test. After the merge, the tests run again on
main, because the combination of your change and someone else's is new. - Build once. The pipeline packs the API into a container image and tags it with the commit's ID, its SHA. That image is the artifact. It never gets rebuilt for production.
- Staging. The image is deployed to staging, a copy of production with its own database and fake test shops. Then a smoke test runs: a few requests that prove the new version is up and the core flow answers.
- Production. After a person checks the release checklist and says go, the same image goes to production, followed by the same smoke test.
Building once matters more than it looks. If production gets its own fresh build, a dependency could have changed in the hour between the two builds, and production would run code nobody tested. With one image tagged 7f3c2e1, you can say exactly what is running where, and going back means deploying the previous tag, which already exists. Containers themselves are covered in Shipping safely; for this course, all you need is that a container platform such as Render, Fly.io, Railway or a cloud provider's container service can run your image.
Quick check
Staging passed with image 7f3c2e1. To save time, a teammate suggests the production job runs docker build again from the same commit. What is the problem?
A rebuild pulls the base image and packages again, and either can change between two builds. Promote the image that staging tested. The tempting first answer assumes builds are perfectly repeatable, which they rarely are without extra work.