Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Deploying the Full App  ›  Capstone Project

Ship on Every Push

GitHub Actions 15 minA CI/CD pipeline tests and builds every change on a fresh machine and, once a change reaches main and passes, deploys it in a fixed order of migrate, restart and health check, using secrets kept outside the code.

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 to main, which is what a merge produces.
  • needs: test: deploy only starts after the test job succeeds.
  • if: deploy only for main, 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.

One Sprout change, from branch to liveStep 1 of 4
  1. Branch and pull request

    A teammate adds a feature on a branch and opens a pull request.

Drills

Answer fast — trust the rule.

quick drill

Why does the pipeline run npm ci rather than npm install?

quick drill

What does needs: test on the deploy job do?

quick drill

A deploy adds a column that the new code reads. In what order should the pipeline act?

quick drill

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.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainFrom hand deploys to a pipeline

Sprout's team deploys by hand: SSH in, git pull, npm install, restart. Last week someone forgot to run migrations and the site broke. Explain how you would turn this into a CI/CD pipeline, and which of the team's problems each part solves.