Artifact Promotion

How application builds are stored centrally and promoted to environments.

Opsitron separates building from deploying. Application code is built once, stored in a shared account, and then promoted to each environment. This ensures you deploy the exact same artifact to staging and production — no “works on dev but fails on prod” surprises.

The Architecture

Application Repo (your code)
  │
  │  push to main
  ▼
Build Workflow (GitHub Actions)
  │
  │  build once
  ▼
SharedServices Account
  ├── ECR Repository (containers)
  │     └── sha-abc1234
  └── S3 Artifact Bucket (static sites)
        └── builds/my-app/abc1234.zip
  │
  │  deploy = pull from shared → push to environment
  ▼
Workload Accounts
  ├── Dev (auto-deploy on build)
  ├── Staging (staff approval)
  └── Production (staff + client approval)

Build Once, Deploy Many

Container Applications

When you push to your app’s main branch:

  1. Build — GitHub Actions builds a Docker image from your Dockerfile
  2. Tag — Image is tagged with the git short SHA (e.g., sha-abc1234)
  3. Push — Image is pushed to the app’s dedicated ECR repository in the SharedServices account
  4. Notify — The build workflow notifies Opsitron of the new artifact

The same image tag (sha-abc1234) is used across all environments. When you deploy to dev, staging, and production, each environment pulls the identical image from the shared ECR.

Static Sites

When you push to your static site’s main branch:

  1. Build — GitHub Actions runs your build script (Hugo, Vite, Next.js, etc.)
  2. Package — Build output is zipped
  3. Upload — Zip is uploaded to the SharedServices S3 artifact bucket at builds/{app-slug}/{git-sha}.zip
  4. Notify — The build workflow notifies Opsitron of the new artifact

When deploying, the artifact zip is downloaded from SharedServices and synced to the environment’s website S3 bucket.

Why SharedServices?

All build artifacts live in a single SharedServices account rather than in each workload account:

  • One build, many deploys — the same artifact is promoted to each environment without rebuilding
  • Cross-account access — workload accounts have read access to SharedServices via AWS Organizations policies
  • Centralized lifecycle — ECR lifecycle policies and S3 versioning are managed in one place
  • Audit trail — every artifact is tagged with its git SHA, creating a direct link from deployed code to source

ECR: One Repository Per Application

Each container application gets its own ECR repository in SharedServices, named {namespace}/{app-slug}:

920534281604-acme/web-app       ← web-app images
920534281604-acme/api-service   ← api-service images

Each repository has:

  • Immutable tags — once sha-abc1234 is pushed, it can never be overwritten
  • Lifecycle policy — keeps the latest 30 tagged images, expires untagged after 30 days
  • Cross-account pull — all workload accounts in the organization can pull images

Container Deployment Flow

When Opsitron deploys a container to an environment:

  1. Update SSM — writes the new image tag to /{app}/{env}/container-image-tag
  2. Get current task definition — reads the ECS task definition from the target cluster
  3. Register new revision — creates a new task definition with the updated image tag
  4. Update ECS service — points the service to the new task definition revision
  5. Wait for stability — ECS rolls out new tasks with the new image, health checks pass
  6. Callback — reports success/failure back to Opsitron

The deploy workflow reads all configuration from SSM parameters (cluster name, service name, image tag param) — it’s self-contained and can be run manually from the GitHub Actions UI without Opsitron.

Static Site Deployment Flow

When Opsitron deploys a static site to an environment:

  1. Download artifact — fetches the zip from SharedServices S3
  2. Sync to website bucketaws s3 sync with smart cache headers:
    • Hashed assets (CSS, JS) → Cache-Control: max-age=31536000, immutable
    • HTML and JSON → Cache-Control: max-age=0, must-revalidate
  3. Invalidate CloudFront/* invalidation ensures fresh content
  4. Update SSM — records the deployed artifact key
  5. Callback — reports success/failure back to Opsitron

Auto-Deploy to Dev

For both container and static site applications, pushes to main automatically deploy to the dev environment:

Push to main → Build → Notify Opsitron → Auto-deploy to dev1

This is controlled by the auto_deploy_to_dev setting on the Application. Staging and production deployments always require explicit approval.

Promotion Pipeline

Build → Dev (auto) → Staging (staff approval) → Production (client approval)

At each stage, the same artifact is deployed — the image tag or artifact key doesn’t change. What changes is the environment it’s deployed to, with progressively stricter approval requirements.

OIDC Authentication

Build and deploy workflows authenticate to AWS using OIDC federation — no stored credentials. GitHub Actions assumes an IAM role in your management account, then chains to the target account:

GitHub OIDC → Management Account Role → SharedServices Role (build)
GitHub OIDC → Management Account Role → Workload Account Role (deploy)

This means:

  • No AWS access keys stored as GitHub secrets
  • Roles have least-privilege permissions
  • Sessions are short-lived (1 hour max)
  • Audit trail in CloudTrail shows exactly which workflow assumed which role