Skip to content

Add a new service to the deploy pipeline

Deploying to the fleet is deploy-by-convention Deployed. You do not write a pipeline. Every deployable repo carries two tiny caller workflows — one for staging, one for prod — and each just calls the one shared reusable engine that lives in a single repo. Adding a service means dropping those two files in, setting a handful of inputs, and confirming the org-level secrets exist. That’s it.

  • Directorycore-your-service/
    • Dockerfile the image the engine builds and pushes
    • Directory.github/workflows/
      • deploy-aca-staging.yml push: staging → engine @staging
      • deploy-aca-prod.yml push: prod → engine @prod
      • ci.yml lint / typecheck / test gate on PRs + branch pushes

The two deploy-aca-*.yml files are intentionally dumb: they pick the environment, name the app, declare ingress/ports, and forward all org secrets with secrets: inherit. All the real work (mint a build token, build + push to ACR, inject env, az containerapp create/update, publish the FQDN) happens inside service-template/.github/workflows/deploy-aca-template.yml.

  1. Decide the shape. Two shapes deploy through the same engine:

    • Ingress service (a Go data plane, a Python gateway, the frontend): accepts inbound traffic, gets a public FQDN, participates in service discovery. Set enable_ingress: true and a target_port.
    • No-ingress worker (any tool-fleet queue worker): no HTTP, no FQDN — it reaches Redis, core-database, core-storage, etc. as a client. Set enable_ingress: false and omit ports entirely.

    See Backend at a glance for the three-shapes lens.

  2. Add deploy-aca-staging.yml. Pick the matching variant. Note cancel-in-progress: true on staging so a newer push supersedes an in-flight one.

    .github/workflows/deploy-aca-staging.yml
    name: Deploy Staging
    on:
    push:
    branches: [ "staging" ]
    workflow_dispatch:
    concurrency:
    group: ${{ github.workflow }}-${{ github.ref }}
    cancel-in-progress: true
    jobs:
    deploy-staging:
    uses: soundversegit/service-template/.github/workflows/deploy-aca-template.yml@staging
    with:
    github_environment: "staging"
    service_name: "core-your-service-staging" # rename per service
    dockerfile_path: "Dockerfile"
    enable_ingress: false # worker: no inbound traffic
    pass_github_token_to_docker: true # clone private soundverse* deps at build
    secrets: inherit
  3. Add deploy-aca-prod.yml. Byte-for-byte the same shape as staging, with three changes: pin the engine at @prod, set github_environment: "prod", and use a -prod service-name suffix. Prod sets cancel-in-progress: false (never abandon a prod rollout mid-flight):

    .github/workflows/deploy-aca-prod.yml
    on:
    push:
    branches: [ "prod" ]
    workflow_dispatch:
    concurrency:
    group: ${{ github.workflow }}-${{ github.ref }}
    cancel-in-progress: false
    jobs:
    deploy-prod:
    uses: soundversegit/service-template/.github/workflows/deploy-aca-template.yml@prod
    with:
    github_environment: "prod"
    service_name: "core-your-service-prod"
    dockerfile_path: "Dockerfile"
    enable_ingress: false # match your staging shape
    pass_github_token_to_docker: true
    secrets: inherit
  4. Name the app correctly — the suffix is load-bearing. The -staging / -prod suffix on service_name is stripped by the engine when it publishes the FQDN back as an org variable. Deploying core-your-service-staging publishes STAGING_CORE_YOUR_SERVICE_GRPC, which is then injected as CORE_YOUR_SERVICE_GRPC into every other service on its next deploy. Workers get no FQDN (nothing is published). See Environment & service discovery.

  5. Confirm the org secrets exist. Config is not per-repo — it lives as org-level Variables and Secrets on the soundversegit org, and secrets: inherit forwards all of them. A brand-new service usually needs to add nothing, because the engine’s own secrets and the shared fleet config already exist org-wide (names only — never values):

    Kind Name(s) Purpose
    Secret AZURE_CREDENTIALS the deploy identity (azure/login)
    Secret GO_PUBLISH_APP_ID, GO_PUBLISH_APP_PRIVATE_KEY GitHub App token to clone private soundverse* deps at build
    Secret GH_READ_REPO_TOKEN fallback private-repo read token
    Secret STAGING_INTERNAL_RPC_SECRET / PROD_… internal Bearer auth between services
    Secret STAGING_REDIS_PASSWORD / PROD_… Redis auth
    Variable STAGING_REDIS_ADDR, STAGING_CORE_*_GRPC connection wiring (the _GRPC vars are auto-published on deploy)

    You only add a new org secret if your service needs a new provider key. Add it under both prefixes (STAGING_<NAME> and PROD_<NAME>); the engine strips the prefix and injects it as <NAME> (a plain env var for a Variable, a secretref for a Secret). Full catalog: Environment variable catalog.

  6. Deploy. Push (or fast-forward merge) to staging; the workflow runs on push. For an off-cycle run use workflow_dispatch and watch it:

    Terminal window
    gh workflow run deploy-aca-staging.yml --ref staging
    gh run watch

    Then promote to prod the same way once staging is verified. See Deploy a service for the full deploy/promote loop and Roll back a deployment if it goes wrong.

The complete input surface of the reusable engine. Only the three required inputs and your ingress shape usually matter; the rest have sane defaults.

Input Type Default Purpose
github_environment string required Picks the STAGING_ / PROD_ prefix for env injection
service_name string required ACA app name; the -staging / -prod suffix is stripped when publishing the FQDN var
dockerfile_path string required Path to the Dockerfile the engine builds
enable_ingress boolean true false for background workers — no FQDN, no inbound
is_external boolean false true = public ingress on :443 (TLS at ACA); false = in-env only
target_port number 80 The container port your app listens on
ingress_transport string http2 auto / http / http2 / tcp; gRPC uses http2, a REST gateway overrides to http
health_check_path string "" If set, injects an HTTP readiness probe (only core-identity uses it, /healthz)
pass_github_token_to_docker boolean false Mounts a short-lived token into BuildKit to clone private soundverse* deps
requires_proto_go_repo boolean false Clones soundverse-proto-go before the build (Go data services)
proto_go_ref string staging Branch/tag of proto-go to check out
resource_group string rg-core Azure resource group
aca_environment string env-soundverse-core ACA environment
acr_name string soundversegeneral Azure Container Registry

Secrets are all passed via secrets: inherit: AZURE_CREDENTIALS (required) plus the optional GO_PUBLISH_APP_ID / GO_PUBLISH_APP_PRIVATE_KEY / GH_READ_REPO_TOKEN.