Orchestration specification

Static Site Delivery

make deploy is the whole deployment model: build the site, initialise and apply its Terraform root, upload public/, and invalidate the CDN.

Applies at
scripts/deploy.sh
Status
Stable

01 Defines

A single short script, reached through make deploy, that takes a static site from source to a live CloudFront distribution in five ordered steps against a chosen Terraform root.

02 Applies when

  • A repository publishes a static site to an S3 bucket behind CloudFront.
  • Developers need their own disposable copy of the site alongside the canonical environments.
  • The same deployment has to run identically from a laptop and from CI.

03 Boundaries

  • The script selects a root and runs it; the infrastructure itself is defined by the published module the root composes.
  • Credentials, backends, and domains belong to each Terraform root, not to the script.
  • Building the site is delegated to make check.hugo; the script does not know how Hugo is invoked.

Expected behaviour

  1. 01
    ENV selects the root, and workspace is the default, so a bare make deploy can only touch your own copy.
  2. 02
    make deploy.plan previews changes without applying, and make deploy.destroy tears the instance down.
  3. 03
    The workspace root selects or creates a Terraform workspace before planning or applying.
  4. 04
    public/ is uploaded in two cache tiers and the distribution is invalidated with /* after every apply.
  5. 05
    The script prints the deployed environment and its site URL on success.

Behaviour#

Deployment is five steps, and make deploy is all five. They live in one script that is short enough to read before running it, and the make targets are one-line wrappers that pass ENV through.

Flowmake deploy
  1. 01 Build make check.hugo renders every site into public/ Static Websites
  2. 02 Init terraform init -input=false in the selected root
  3. 03 Apply Provisions the S3 bucket, CloudFront distribution, ACM certificate, and DNS records Pinned Module Composition
  4. 04 Upload Two aws s3 sync passes with different Cache-Control headers Fingerprinted Cache Tiers
  5. 05 Invalidate create-invalidation --paths '/*' on the distribution

plan and destroy stop after init (and workspace selection): plan prints the change set, and destroy removes everything, bucket contents included, because the stack bucket sets force_destroy.

Roots#

Each ENV maps to a Terraform root. Every root composes the same published module at the same pinned version, so they differ only in backend, domain, and credentials.

ENVRootInstance
workspaceworkspaces/siteOne per developer or branch, named by Terraform workspace
prodenv/prod/releaseProduction

The workspace root is the only one that selects a Terraform workspace. Its name comes from DEPLOY_WORKSPACE, then the CI branch name, then the local username, which is how each developer gets their own state, bucket, and hostname — see Derived Developer Identity .

Examples#

Root selection and the developer workspace are resolved before any command runs:

scripts/deploy.sh
case "$environment" in
	workspace) root=workspaces/site ;;
	staging) root=env/staging/release ;;
	prod) root=env/prod/release ;;
esac

terraform -chdir="$root" init -input=false

if [ "$root" = workspaces/site ]; then
	terraform -chdir="$root" workspace select -or-create \
		"${DEPLOY_WORKSPACE:-${GITHUB_REF_NAME:-$(id -un)}}"
fi

Day to day, the make targets are the interface:

make deploy                 # your own copy
make deploy ENV=prod
make deploy.plan            # preview, changes nothing
make deploy.destroy         # tear the instance down

Rolling back is the same command run from an earlier commit: check it out and make deploy again. The bucket is versioned, so earlier object versions stay recoverable even without the pipeline.