Docker and deploy
Services opt into images by enabling sbt-native-packager's
DockerPlugin. Deploying to multiple environments fans out over typed targets.
Green is the image path (plugin → Aggregate Docker/publish). Amber is the target fan-out: one deploy job per
Target, each wired to its own GitHub Environment (approvals stay independent).
Docker paved path
lazy val service = project
.dependsOn(coreLib)
.enablePlugins(JavaAppPackaging, DockerPlugin)
.settings(
publishArtifact := false,
Compile / mainClass := Some("example.Main"),
dockerBaseImage := "eclipse-temurin:21-jre",
Docker / packageName := "example-service",
)
zipx detects DockerPlugin and emits a release-gated Aggregate docker job joining …/Docker/publish (or use
dockerGraph). Multi-registry pushes are a custom capability with targets (see Custom capabilities). For
PR-label stage ECR (before merge), see Job conditions.
DocsRender.job("docker")(Capability.docker)docker:
name: docker
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: Setup JDK 21
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95
with:
distribution: temurin
java-version: "21"
- uses: sbt/setup-sbt@d059c39de700f4cc5cb64f9f56577315e44a984e
with:
disk-cache: "false"
- name: Cache sbt
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: |
~/.sbt
~/.cache/sbt
~/.cache/coursier
target
key: ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-docker
restore-keys: |
ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-
ubuntu-latest-jdk21-sbt-0.1.0-ci-
ubuntu-latest-jdk21-sbt-0.1.0-
ubuntu-latest-jdk21-sbt-
- name: docker
run: sbt 'service/Docker/publish'Aggregate-by-target deploy
Default (Capability.deploy / zipxTasks.deploy): one job per Target; participating modules' commands are joined.
GitHub Environments stay independent.
Escape hatch (Capability.deployGraph / zipxTasks.deployGraph): one job per (module × target).
val promote = taskKey[Unit]("promote the image")
zipxCapabilities += zipxTasks.deploy(
participates = _.id == "service",
command = promote,
targets = _ => List(
Target("staging", env = Map("TIER" -> EnvValue.plain("staging"))),
Target(
"prod",
environment = Some("production"),
env = Map("TIER" -> EnvValue.plain("prod"), "DEPLOY_ROLE" -> secret"PROD_ROLE"),
condition = Some(JobCondition.refIs("refs/heads/main")),
),
),
needsCapabilities = List("docker"),
permissions = Map("id-token" -> "write", "contents" -> "read"),
)
{
val targets = List(
Target("staging", env = Map("TIER" -> EnvValue.plain("staging"))),
Target(
"prod",
environment = Some("production"),
env = Map("TIER" -> EnvValue.plain("prod"), "DEPLOY_ROLE" -> secret"PROD_ROLE"),
condition = Some(JobCondition.refIs("refs/heads/main")),
),
)
DocsRender.jobs("deploy-staging", "deploy-prod")(
Capability.deploy(
participates = _.id == "service",
command = n => s"${n.id}/promote",
targets = _ => targets,
needsCapabilities = Nil,
)
)
}deploy-staging:
name: deploy (staging)
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
env:
TIER: staging
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: Setup JDK 21
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95
with:
distribution: temurin
java-version: "21"
- uses: sbt/setup-sbt@d059c39de700f4cc5cb64f9f56577315e44a984e
with:
disk-cache: "false"
- name: Cache sbt
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: |
~/.sbt
~/.cache/sbt
~/.cache/coursier
target
key: ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-deploy-staging
restore-keys: |
ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-
ubuntu-latest-jdk21-sbt-0.1.0-ci-
ubuntu-latest-jdk21-sbt-0.1.0-
ubuntu-latest-jdk21-sbt-
- name: deploy
run: sbt 'service/promote'
deploy-prod:
name: deploy (prod)
runs-on: ubuntu-latest
if: (startsWith(github.ref, 'refs/tags/v')) && (github.ref == 'refs/heads/main')
environment: production
env:
DEPLOY_ROLE: ${{ secrets.PROD_ROLE }}
TIER: prod
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: Setup JDK 21
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95
with:
distribution: temurin
java-version: "21"
- uses: sbt/setup-sbt@d059c39de700f4cc5cb64f9f56577315e44a984e
with:
disk-cache: "false"
- name: Cache sbt
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: |
~/.sbt
~/.cache/sbt
~/.cache/coursier
target
key: ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-deploy-prod
restore-keys: |
ubuntu-latest-jdk21-sbt-0.1.0-ci-${{ github.run_id }}-
ubuntu-latest-jdk21-sbt-0.1.0-ci-
ubuntu-latest-jdk21-sbt-0.1.0-
ubuntu-latest-jdk21-sbt-
- name: deploy
run: sbt 'service/promote'Approval is enforced by GitHub, not zipx. zipx emits the environment: binding; GitHub pauses for protection
rules. Put deploy config in project/*.scala as typed lists (see
examples/monorepo).