Ship on Every Push
Deploying Sprout by hand (SSH in, pull the code, install, restart) works until someone forgets a step, ships code nobody tested, or does it tired at midnight. A pipeline makes shipping boring in the best way: the same steps, in the same order, every time, run by a machine.
The rule
Every change gets tested; every merge to main gets deployed.
Continuous integration (CI): on every pull request, a fresh machine installs both halves from their lockfiles, runs the tests and builds the client. A red result blocks the merge.
Continuous deployment (CD): when main changes and the tests pass, the pipeline ships: build the client, copy the files to the server, run database migrations, restart the API and check its health.
GitHub Actions runs workflows described in YAML files under .github/workflows/:
name: ci-cd
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci && npm test
working-directory: server
- run: npm ci && npm run build
working-directory: client
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}on: run for every pull request, and for every push tomain, which is what a merge produces.needs: test: deploy only starts after the test job succeeds.if: deploy only formain, never for a pull request's code.secrets: the SSH key lives in the repository's encrypted secrets store and is handed to the step at run time. It never appears in the YAML.
Order matters inside deploy.sh. Migrate first, then restart, then check health. New code that expects a new table must not start before the table exists. If the health check fails, switch back to the previous release. That is why migrations are written to add things rather than break what the old code still uses.
Tools such as Ansible (which configures servers from files) and Terraform (which creates cloud resources from files) take the same idea one step further: the servers themselves become code in the repository, reviewed and repeatable.
The shortcut
Write down today's manual deploy as numbered steps. Each step becomes a pipeline step, and any step that relies on someone remembering ("don't forget the migrations!") is exactly the one the pipeline must do for you.
Branch and pull request
A teammate adds a feature on a branch and opens a pull request.
Drills
Answer fast — trust the rule.
Why does the pipeline run npm ci rather than npm install?
What does needs: test on the deploy job do?
A deploy adds a column that the new code reads. In what order should the pipeline act?
Where should the SSH key used for deploying live?
Remember
- Every change gets tested; every merge to main gets deployed.
- Shortcut: each manual step becomes a pipeline step, especially the ones people forget.
- Trap: restarting before migrating, and secrets written into workflow files.
