Skip to content

The deployment files in your fork

Read appspec.yml, the buildspec and the four hook scripts so nothing in the pipeline is magic.

About 15 min · Verified 8 October 2026

0 of 3 steps done0%

The pipeline is driven by files that already live in your fork. You do not need to change them for the backend, but you should know what each does, because when a deployment fails the error message names one of them.

Files used by the backend pipeline
appspec.yml                    tells CodeDeploy what to copy and which scripts to run
deploy/buildspec-api.yml       tells CodeBuild how to test and package
deploy/hooks/before_install.sh stop the service, check the env file
deploy/hooks/after_install.sh  install dependencies, apply the schema
deploy/hooks/start.sh          write the systemd unit and restart the service
deploy/hooks/validate.sh       wait for /health to report the database is up

buildspec-api.yml: test and package#

deploy/buildspec-api.yml
version: 0.2
phases:
  install:
    runtime-versions:
      nodejs: 20
  build:
    commands:
      - npm --prefix shortlink-api ci
      - npm --prefix shortlink-api test
artifacts:
  files:
    - appspec.yml
    - deploy/hooks/**/*
    - shortlink-api/**/*
  exclude-paths:
    - shortlink-api/node_modules/**/*
    - shortlink-api/.env
    - shortlink-api/.env.*
LineMeaning
runtime-versions: nodejs: 20Ask the CodeBuild image for Node.js 20. You do not pick a runtime in the console, the file does
npm ci then npm testInstall exactly the locked dependencies, then run the tests. A failing test fails the build and the pipeline never reaches deploy
artifacts.filesWhat goes into the ZIP that CodeDeploy receives. appspec.yml must be at the ZIP root
exclude-pathsNever ship node_modules (the server installs its own) or any .env file

appspec.yml: what CodeDeploy does on the server#

appspec.yml
version: 0.0
os: linux
files:
  - source: shortlink-api
    destination: /opt/shortlink/app
file_exists_behavior: OVERWRITE
permissions:
  - object: /opt/shortlink/app
    owner: ubuntu
    group: ubuntu
    type:
      - file
      - directory
hooks:
  BeforeInstall:
    - location: deploy/hooks/before_install.sh
      timeout: 60
      runas: root
  AfterInstall:
    - location: deploy/hooks/after_install.sh
      timeout: 300
      runas: root
  ApplicationStart:
    - location: deploy/hooks/start.sh
      timeout: 60
      runas: root
  ValidateService:
    - location: deploy/hooks/validate.sh
      timeout: 120
      runas: root
  • files copies the shortlink-api folder from the bundle to /opt/shortlink/app. Note that this is a different folder from the manual checkout in /opt/shortlink/repo.
  • OVERWRITE lets a new release replace files from the last one.
  • permissions makes ubuntu own the copied files so the service (which runs as ubuntu) can read them.
  • Each hook is a script run at one moment of the deployment, with a timeout. A non-zero exit code fails the deployment.

The hook scripts#

deploy/hooks/before_install.sh
#!/usr/bin/env bash
set -euo pipefail
test -s /etc/shortlink/shortlink.env
sudo -u ubuntu test -r /etc/shortlink/shortlink.env
systemctl stop shortlink-api
install -d -m 755 -o ubuntu -g ubuntu /opt/shortlink/app

Fails fast if the private env file is missing or unreadable, stops the running service, and makes sure the target folder exists.

The order things run in#

With a load balancer attached, CodeDeploy wraps your hooks with traffic control:

OrderEventWho runs it
1BeforeBlockTraffic, BlockTraffic, AfterBlockTrafficCodeDeploy takes the server out of the target group and waits for connections to drain (your 30-second deregistration delay)
2ApplicationStopA script from the previous revision. This app has none
3DownloadBundleThe agent fetches the ZIP from the artifact bucket
4BeforeInstallbefore_install.sh
5InstallCodeDeploy copies the files
6AfterInstallafter_install.sh
7ApplicationStartstart.sh
8ValidateServicevalidate.sh
9BeforeAllowTraffic, AllowTraffic, AfterAllowTrafficCodeDeploy registers the server again; the target group health check must pass

Check your fork#

Confirm the files are where the pipeline expects them#

From the root of your clone:

Your computerList the deployment files and their Git modes
git ls-files -s appspec.yml deploy/buildspec-api.yml deploy/hooks
Expected output
100644 … 0	appspec.yml
100644 … 0	deploy/buildspec-api.yml
100755 … 0	deploy/hooks/after_install.sh
100755 … 0	deploy/hooks/before_install.sh
100755 … 0	deploy/hooks/start.sh
100755 … 0	deploy/hooks/validate.sh

appspec.yml must be at the repository root, and each .sh file must be 100755 (executable).

Fix a missing executable bit (only if you saw 100644 on a hook)#

Your computerMake the hooks executable and commit
git update-index --chmod=+x deploy/hooks/before_install.sh deploy/hooks/after_install.sh deploy/hooks/start.sh deploy/hooks/validate.sh
git commit -m "fix: make deployment hooks executable"
git push origin main

Do not push yet if you have not finished creating the pipeline; a push now is harmless but will not start a build until the pipeline exists.

Make sure your fork is up to date on GitHub#

Your computerNothing uncommitted, nothing unpushed
git status --short
git fetch origin
git log --oneline origin/main..HEAD

Both commands should print nothing. CodePipeline reads GitHub, not the files on your laptop. Anything not pushed does not exist as far as AWS is concerned.

Next: prepare the EC2 server.

Found a mistake? Edit this page on GitHub.