ci-cd-best-practices

Apply Dedalus CI/CD workflow rules for runner placement, Docker build verification, artifact publication, and self-hosted runner orchestration. Use when editing GitHub Actions workflows, Docker build pipelines, deploy jobs, Terraform/ECS/Flux workflows, or self-hosted runner automation.

Category
Engineering
Skill
021 of 261
Source
Kevin wiki

Hollywood workflow generation

Do not handwrite new GitHub Actions workflow YAML. Author or edit the Hollywood TypeScript source, then regenerate .github/workflows/*.yml.

Default workflow flow:

  1. Put workflow source under ci/ and export a Hollywood workflow(...) object.
  2. Generate with pnpm exec hollywood generate "ci/**/*.ts" --output ..
  3. Commit both the source and generated YAML. Generated YAML must begin with # @generated by Hollywood. Do not edit by hand.
  4. If Hollywood cannot express a required GitHub Actions field, extend Hollywood first instead of hand-editing YAML.

Existing handwritten workflows are legacy precedent, not permission to add more handwritten YAML. When touching a legacy workflow for substantial behavioral changes, prefer moving that workflow behind Hollywood in the same PR or split the generator migration into a preparatory PR.

Runner placement

Choose the cheapest runner that matches the job's real bottleneck.

ubuntu-24.04

Use for I/O-bound or orchestration-heavy jobs:

  • git / GitHub API orchestration
  • AWS/Terraform apply jobs that do not compile code
  • artifact upload/download
  • release-please
  • branch promotion and audit tagging
  • Copybara sync jobs
  • IAM preflight / policy simulation
  • ECS force deploy
  • link checking
  • small lint/validation jobs that do not build or test significant code
  • self-hosted runner provision / cleanup control jobs

blacksmith-4vcpu-ubuntu-2404

Use for compute-bound jobs:

  • Docker builds
  • Rust / Go / Python / TypeScript builds
  • unit, integration, and end-to-end tests
  • typechecking over real codebases
  • static analysis that scans large codebases (for example Semgrep)
  • Kind / KWOK / Chaos / stress workloads
  • anything that spends real time compiling, bundling, or executing test suites

self-hosted AWS runners

Use only when GitHub-hosted runners cannot do the job:

  • KVM / nested virtualization
  • ARM64-native builds that must run on our own infrastructure
  • privileged or hardware-specific workloads
  • deploy jobs whose payload must run inside the target architecture/runtime

If the job is only provisioning or cleaning up the self-hosted runner, run that control job on ubuntu-24.04. Only the payload belongs on self-hosted.

Docker artifact policy

PR CI

Every deployable image must be built in CI with push: false.

Rules:

  • CD must never be the first place a Dockerfile runs
  • do not push unreviewed application images to ECR from pull requests
  • verify the exact Dockerfile, build context, and target platform the CD workflow uses
  • make CI gates and CD path filters cover every file copied into that Docker build context
  • if the Dockerfile depends on private ECR base images, log in only for pull access; still keep push: false

merge / branch CD

After merge, CD may build and publish the application image for the merged commit.

Current repo rule:

  • PRs verify builds
  • merged branches publish images

If you later refactor a service toward true build-once/deploy-later, keep the same invariant: no unreviewed artifact is written to ECR.

Workflow structure

Prefer these shapes:

  1. verify-build in CI
  2. publish in CD
  3. deploy/apply in CD

Split build from deploy when runner classes differ. A pure Terraform/ECS apply job should not stay on Blacksmith just because the build job in the same workflow needs it.

Use reusable workflows for generic behavior. Use service-specific wrapper workflows when a service needs custom setup such as:

  • private base image bootstrap
  • extra build contexts
  • special build arguments
  • architecture-specific setup

Self-hosted runner guidance

Prefer server-side runner registration when the platform supports it.

For GitHub Actions runners:

  • prefer JIT runner configuration over registration-token plus polling loops
  • avoid scanning the runner list in a retry loop when the API can return the runner identity directly
  • keep provisioning and cleanup logic separate from the payload job

Validation

After editing workflows:

  1. Regenerate workflows with pnpm exec hollywood generate "ci/**/*.ts" --output .
  2. Run actionlint
  3. Check that cheap jobs use ubuntu-24.04
  4. Check that builds/tests remain on Blacksmith or self-hosted
  5. Confirm PR image verification uses push: false
  6. Confirm CD still publishes only from merged branches or explicit deploy triggers