Docker and deploy
Skip until you ship a service image or more than one environment. Enable
sbt-native-packager's DockerPlugin on the module; zipx adds the docker
job. Targets are named environments (staging, production) with separate GitHub Environment approvals.
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).
Where they run is one setting. By default (DeployTrigger.OnMerge) images and deploys are jobs in ci.yml, gated
like any other job: on a release tag, or on a condition you add. DeployTrigger.Manual moves them into a dispatched
zipx-deploy.yml that ships only what changed since each Environment's last deploy, so a merge ships nothing and a
pending production approval never holds up the next merge. DeployTrigger.Staged runs the same workflow on every merge
and on labeled PRs too, for pre-production targets only. The sections below apply to all three; the last two cover
Manual and Staged. CI for a busy monorepo shows them beside the other settings a large repo turns on.
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). Pushing one image to several registries stays one job; see Registries are destinations, targets are
environments below. For PR-label stage ECR (before merge), see Job conditions. Ship / ShipGroup rows
release libraries from zipx-release.yml; the image and deploy still wait on a human v* tag in ci.yml. See
Independent versions.
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: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: docker
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: docker
run: sbt 'service/Docker/publish'Registries are destinations, targets are environments
The rule of thumb, because getting it backwards is expensive:
| The destinations are | Shape | Why |
|---|---|---|
| Registries for one image | withSharedTargets (TargetFanOut.SharedJob) | Docker / publish builds once and pushes every dockerAliases entry, so N registries is one job |
| Deploy environments | withTargets (TargetFanOut.JobPerTarget, the default) | Each really is a separate job: its own approval, its own environment:, its own if: |
targets multiplies jobs. That is right for the second row and wrong for the first: 6 registries across 8 images is
48 jobs under JobPerTarget and 8 under SharedJob, and the 48 each rebuild the same image, so nothing guarantees
the registries hold identical bytes. One build pushed N times does guarantee it.
zipxCapabilities += Capability.docker.withSharedTargets(
List(
Target(TargetName("us"), env = Map("AWS_REGION" -> EnvValue.plain("us-east-1"), "AWS_ROLE_TO_ASSUME" -> secret"US_ROLE")),
Target(TargetName("eu"), env = Map("AWS_REGION" -> EnvValue.plain("eu-west-1"), "AWS_ROLE_TO_ASSUME" -> secret"EU_ROLE")),
)
).copy(extraSteps = ZipxAws.sharedLoginSteps)
One job, one image, OIDC then ECR login per destination. On AWS, ZipxAws.dockerPublishAll(registries) is that whole
expression (see Packs).
DocsRender.job("docker")(
Capability.docker.withSharedTargets(
List(
Target(
TargetName("us"),
env = Map("AWS_REGION" -> EnvValue.plain("us-east-1"), "AWS_ROLE_TO_ASSUME" -> secret"US_ROLE"),
),
Target(
TargetName("eu"),
env = Map("AWS_REGION" -> EnvValue.plain("eu-west-1"), "AWS_ROLE_TO_ASSUME" -> secret"EU_ROLE"),
),
)
)
)docker:
name: docker
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
env:
ZIPX_EU_AWS_REGION: eu-west-1
ZIPX_EU_AWS_ROLE_TO_ASSUME: ${{ secrets.EU_ROLE }}
ZIPX_US_AWS_REGION: us-east-1
ZIPX_US_AWS_ROLE_TO_ASSUME: ${{ secrets.US_ROLE }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: docker
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: docker
run: sbt 'service/Docker/publish'Why the env keys are prefixed
Both destinations want AWS_ROLE_TO_ASSUME. Merging unprefixed would keep whichever one came last, and the job would
push twice to one account while silently skipping the other, so a shared job puts each destination's env under
Target.envKey: ZIPX_<TARGET>_<KEY>. Target.envName(name) is how a step reads it back, and the fixed ZIPX_
anchor is what makes that total: a target named github would otherwise derive a GITHUB_… name, which EnvName
refuses because GitHub reserves the prefix.
extraSteps receives every destination as StepContext.destinations (and StepContext.target is None, since there
is no single target a shared job belongs to), which is how one bundle emits one login per registry.
What a shared job refuses
A Target.condition or Target.environment under SharedJob is a generate-time error, not a silently dropped
field:
zipx: capability 'docker' target 'us' sets a condition, which one shared job cannot honor per destination.
Use TargetFanOut.JobPerTarget (the default) when destinations need their own condition, or drop it and gate
the whole job with Capability.condition.
Dropping it would push to a registry the author said to skip; applying it job-wide would skip the ones that were fine.
Both are wrong answers arrived at quietly, so zipx declines to pick one. Per-destination approval is the second row of
the table: that is what JobPerTarget is for.
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(TargetName("staging"), env = Map("TIER" -> EnvValue.plain("staging"))),
Target(
TargetName("prod"),
environment = Some("production"),
env = Map("TIER" -> EnvValue.plain("prod"), "DEPLOY_ROLE" -> secret"PROD_ROLE"),
condition = Some(JobCondition.varNonEmpty("DEPLOY_PROD_ENABLED")),
),
),
needsCapabilities = List(Capability.DockerName),
permissions = Map("id-token" -> "write", "contents" -> "read"),
)
Note what the prod condition is not: refIs("refs/heads/main"). Capability.deploy gates OnReleaseTag, the two are
ANDed, and no ref is both a v* tag and refs/heads/main, so zipx refuses to generate that pair outright (see
[[JobConditions]]). Pass gate = Gate.Always if deploy-from-main is what you want.
{
val targets = List(
Target(TargetName("staging"), env = Map("TIER" -> EnvValue.plain("staging"))),
Target(
TargetName("prod"),
environment = Some("production"),
env = Map("TIER" -> EnvValue.plain("prod"), "DEPLOY_ROLE" -> secret"PROD_ROLE"),
condition = Some(JobCondition.varNonEmpty("DEPLOY_PROD_ENABLED")),
),
)
DocsRender.jobs("deploy-staging", "deploy-prod")(
Capability.deploy(
participates = _.id == "service",
command = n => SbtCommand.module(n, SbtCommand.unsafeTask("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: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: deploy-staging
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: deploy
run: sbt 'service/promote'
deploy-prod:
name: deploy (prod)
runs-on: ubuntu-latest
if: (startsWith(github.ref, 'refs/tags/v')) && (vars.DEPLOY_PROD_ENABLED != '')
environment: production
env:
DEPLOY_ROLE: ${{ secrets.PROD_ROLE }}
TIER: prod
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: deploy-prod
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- 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).
Deploy by hand (DeployTrigger.Manual)
By default images and deploys run in ci.yml, on whatever their gates select. On a busy main branch that means every
merge builds images, and a production approval that nobody answers holds ci.yml's concurrency group, so later merges
queue behind it. DeployTrigger.Manual moves them into their own workflow that someone runs:
zipxDeployTrigger := DeployTrigger.Manual()
// on each image module: what the tag check looks up
zipxImageRefs := (Docker / dockerAliases).value.map(_.toString)
Target(TargetName("stg"), environment = Some("staging"), group = Some(TargetGroup("pre-prod")))
ci.yml keeps Verify and library publish. .github/workflows/zipx-deploy.yml takes the image capability
(Capability.DockerName), every Deploy capability, and everything that needs one. It runs from Actions → Run
workflow with three inputs:
| Input | Values |
|---|---|
modules | changed (default), all, or one module |
target | choose (the default, which deploys nothing), a target name, or a Target.group that deploys every target in it |
sha | a commit on the dispatched branch, for a rollback; empty deploys the branch head |
changed is per Environment. The resolve job reads each Environment's deployments from GitHub and diffs the last
successful deploy of each module against the commit being deployed. A module never deployed there is deployed, and so
is every module when the diff cannot run: an over-deploy costs minutes, an under-deploy ships a stale service. GitHub
records each job that binds an Environment as a deployment of the run's commit, so every deploy job sets its
environment url to …/commit/<sha>#<module>, and that url is what resolve reads back. A rollback is recorded as the
commit it deployed.
An image is pushed once per commit. Image jobs bind the zipx-images Environment so each push is recorded too, and
run <module>/zipxImageMissing first: it checks every zipxImageRefs entry with docker manifest inspect and the push
runs only when one is missing. A rebuild is not byte-identical, and an immutable-tag registry rejects a second push.
Every job checks out the plan's commit and exports it as ZIPX_DEPLOY_SHA; a build that tags images by commit should
read it before GITHUB_SHA. Each job that binds an Environment is its own concurrency group across runs, so two
dispatches reaching one Environment by different choices (prod, or a group containing it) queue instead of
interleaving, and two pushes of one image never race. Nothing is cancelled in progress.
Production deploys only from the default branch. A Target's stage is DeployStage.Production unless it says
otherwise, and resolve refuses a dispatch from any other branch that selects one. The sha input must be an ancestor
of the dispatched branch's head, so a dispatch from main cannot name an unmerged commit. Pair each production
Environment with a deployment branch policy on the default branch: GitHub enforces it even against a workflow edited on
a branch, which no check inside the workflow can.
Generate refuses what the deploy workflow could not run as declared: a capability that is not Graph-scoped, a Verify
capability that needs an image, a need on a capability left in ci.yml, a condition that requires a push (a dispatch
never is one), a deploy target with no Environment, a group named like a target, a target named choose, and a target
declared Production in one capability and PreProduction in another. Release gates do not apply there, since the plan
is the gate. Jobs are always one per module and target: a collapsed matrix binds its Environment on every leg, so a
skipped leg would still wait for approval and record a deploy that never happened.
{
val targets = List(
Target(TargetName("stg"), environment = Some("staging"), group = Some(TargetGroup("pre-prod"))),
Target(TargetName("prod"), environment = Some("production")),
)
val deploys = List(
Capability.dockerGraph.copy(gate = Gate.Always),
Capability.deployGraph(
participates = _.id == "service",
command = n => SbtCommand.module(n, SbtCommand.unsafeTask("promote")),
targets = _ => targets,
gate = Gate.Always,
),
)
DeployWorkflow.render(libGraph, deploys, config, DeployTrigger.Manual()).yaml
}# Generated by zipx. Do not edit. Run 'sbt zipxWorkflowGenerate' to regenerate.
name: zipx deploy
"on":
workflow_dispatch:
inputs:
modules:
description: "changed: what differs from each Environment's last deploy; all; or one module"
type: choice
required: true
default: changed
options:
- changed
- all
- service
target:
description: Target or group to deploy to (choose deploys nothing)
type: choice
required: true
default: choose
options:
- choose
- prod
- stg
- pre-prod
sha:
description: Commit to deploy (40 hex). Empty deploys the dispatched branch head.
type: string
required: false
concurrency:
group: zipx-deploy-${{ inputs.target }}
cancel-in-progress: false
jobs:
resolve:
name: resolve
runs-on: ubuntu-latest
permissions:
contents: read
deployments: read
outputs:
sha: ${{ steps.plan.outputs.sha }}
images: ${{ steps.plan.outputs.images }}
targets: ${{ steps.plan.outputs.targets }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: resolve
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: Resolve deploy plan
id: plan
run: |
sbt zipxDeployPlan
printf '%s\n' "sha=$(cat target/zipx-deploy/sha)" "images=$(cat target/zipx-deploy/images.json)" "targets=$(cat target/zipx-deploy/targets.json)" >> "$GITHUB_OUTPUT"
env:
ZIPX_DEPLOY_MODULES: ${{ inputs.modules }}
ZIPX_DEPLOY_REQUESTED_SHA: ${{ inputs.sha }}
ZIPX_GITHUB_TOKEN: ${{ github.token }}
ZIPX_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
ZIPX_DEPLOY_TARGET: ${{ inputs.target }}
docker-service:
name: docker service
runs-on: ubuntu-latest
needs:
- resolve
if: "!cancelled() && needs.resolve.result == 'success' && contains(fromJson(needs.resolve.outputs.images), 'service')"
environment:
name: zipx-images
url: ${{ github.server_url }}/${{ github.repository }}/commit/${{ needs.resolve.outputs.sha }}#service
concurrency: zipx-deploy-docker-service
env:
ZIPX_DEPLOY_SHA: ${{ needs.resolve.outputs.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.resolve.outputs.sha }}
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: docker-service
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: Check image tags
id: image-tags
run: |
sbt "service/zipxImageMissing"
echo "missing=$(cat target/zipx-image-missing)" >> "$GITHUB_OUTPUT"
- name: docker
if: steps.image-tags.outputs.missing == 'true'
run: sbt 'service/Docker/publish'
deploy-service-prod:
name: deploy service (prod)
runs-on: ubuntu-latest
needs:
- docker-service
- resolve
if: "!cancelled() && needs.resolve.result == 'success' && contains(fromJson(needs.resolve.outputs.targets)['prod'], 'service') && needs.docker-service.result != 'failure'"
environment:
name: production
url: ${{ github.server_url }}/${{ github.repository }}/commit/${{ needs.resolve.outputs.sha }}#service
concurrency: zipx-deploy-deploy-service-prod
env:
ZIPX_DEPLOY_SHA: ${{ needs.resolve.outputs.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.resolve.outputs.sha }}
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: deploy-service-prod
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: deploy
run: sbt 'service/promote'
deploy-service-stg:
name: deploy service (stg)
runs-on: ubuntu-latest
needs:
- docker-service
- resolve
if: "!cancelled() && needs.resolve.result == 'success' && contains(fromJson(needs.resolve.outputs.targets)['stg'], 'service') && needs.docker-service.result != 'failure'"
environment:
name: staging
url: ${{ github.server_url }}/${{ github.repository }}/commit/${{ needs.resolve.outputs.sha }}#service
concurrency: zipx-deploy-deploy-service-stg
env:
ZIPX_DEPLOY_SHA: ${{ needs.resolve.outputs.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.resolve.outputs.sha }}
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: deploy-service-stg
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: deploy
run: sbt 'service/promote'
Staging on merge, branches by label (DeployTrigger.Staged)
Manual makes every deploy a dispatch. Most teams want staging to follow main on its own, a way to try a branch on
staging, and production only when someone asks. DeployTrigger.Staged is that, in the same zipx-deploy.yml:
zipxDeployTrigger := DeployTrigger.staged(deployLabel = "deploy-stg", skipLabel = "no-deploy")
Target(TargetName("stg"), environment = Some("staging"), stage = DeployStage.PreProduction)
Target(TargetName("prod"), environment = Some("production")) // Production, the default
| What happens | What it deploys | To |
|---|---|---|
| A merge to the default branch | Its changed modules, unless the merged PR carries skipLabel | Every PreProduction target |
A push to a PR carrying deployLabel (or adding the label) | The PR head's changed modules, and only the images those need | Every PreProduction target |
| A dispatch from the default branch | Whatever the form selects | Any target |
A branch reaches nothing without the label: an unlabeled PR's run stops at resolve's job condition before a runner
boots sbt, and a dispatch from a branch is refused (the label is the one way a branch deploys). Nothing but a dispatch
from the default branch reaches a Production target; no merge or PR plan can contain one. Generate refuses a
Staged build with no PreProduction target, and one label used for both deploying and skipping.
skipLabel defers; it does not exclude. A merge labeled no-deploy deploys nothing, but staging always deploys
the default branch's head, so the next merge that deploys ships that code too, because changed compares each module's
last deploy with the new commit. That comparison runs in both directions, so the merge after a labeled PR's deploy also
puts the default branch back on every module the branch changed.
Runs from one source share a group: a PR's runs replace each other, merges replace each other, and neither replaces a
pending dispatch. The per-Environment job groups from Deploy by hand still order everything that reaches one
Environment. resolve reads the merged PR's labels through GitHub's GraphQL API, so it needs pull-requests: read,
which zipx emits.
{
val targets = List(
Target(TargetName("stg"), environment = Some("staging"), stage = DeployStage.PreProduction),
Target(TargetName("prod"), environment = Some("production")),
)
val deploys = List(
Capability.dockerGraph.copy(gate = Gate.Always),
Capability.deployGraph(
participates = _.id == "service",
command = n => SbtCommand.module(n, SbtCommand.unsafeTask("promote")),
targets = _ => targets,
gate = Gate.Always,
),
)
DeployWorkflow.render(libGraph, deploys, config, DeployTrigger.staged("deploy-stg", "no-deploy")).yaml
}# Generated by zipx. Do not edit. Run 'sbt zipxWorkflowGenerate' to regenerate.
name: zipx deploy
"on":
push:
branches:
- main
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
workflow_dispatch:
inputs:
modules:
description: "changed: what differs from each Environment's last deploy; all; or one module"
type: choice
required: true
default: changed
options:
- changed
- all
- service
target:
description: Target or group to deploy to (choose deploys nothing)
type: choice
required: true
default: choose
options:
- choose
- prod
- stg
sha:
description: Commit to deploy (40 hex). Empty deploys the dispatched branch head.
type: string
required: false
concurrency:
group: zipx-deploy-${{ github.event_name }}-${{ github.event.pull_request.number }}${{ inputs.target }}
cancel-in-progress: false
jobs:
resolve:
name: resolve
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'deploy-stg')
permissions:
contents: read
deployments: read
pull-requests: read
outputs:
sha: ${{ steps.plan.outputs.sha }}
images: ${{ steps.plan.outputs.images }}
targets: ${{ steps.plan.outputs.targets }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: resolve
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: Resolve deploy plan
id: plan
run: |
sbt zipxDeployPlan
printf '%s\n' "sha=$(cat target/zipx-deploy/sha)" "images=$(cat target/zipx-deploy/images.json)" "targets=$(cat target/zipx-deploy/targets.json)" >> "$GITHUB_OUTPUT"
env:
ZIPX_DEPLOY_MODULES: ${{ inputs.modules }}
ZIPX_DEPLOY_REQUESTED_SHA: ${{ inputs.sha || github.event.pull_request.head.sha }}
ZIPX_GITHUB_TOKEN: ${{ github.token }}
ZIPX_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
ZIPX_DEPLOY_TARGET: ${{ inputs.target }}
ZIPX_DEPLOY_PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
docker-service:
name: docker service
runs-on: ubuntu-latest
needs:
- resolve
if: "!cancelled() && needs.resolve.result == 'success' && contains(fromJson(needs.resolve.outputs.images), 'service')"
environment:
name: zipx-images
url: ${{ github.server_url }}/${{ github.repository }}/commit/${{ needs.resolve.outputs.sha }}#service
concurrency: zipx-deploy-docker-service
env:
ZIPX_DEPLOY_SHA: ${{ needs.resolve.outputs.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.resolve.outputs.sha }}
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: docker-service
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: Check image tags
id: image-tags
run: |
sbt "service/zipxImageMissing"
echo "missing=$(cat target/zipx-image-missing)" >> "$GITHUB_OUTPUT"
- name: docker
if: steps.image-tags.outputs.missing == 'true'
run: sbt 'service/Docker/publish'
deploy-service-prod:
name: deploy service (prod)
runs-on: ubuntu-latest
needs:
- docker-service
- resolve
if: "!cancelled() && needs.resolve.result == 'success' && contains(fromJson(needs.resolve.outputs.targets)['prod'], 'service') && needs.docker-service.result != 'failure'"
environment:
name: production
url: ${{ github.server_url }}/${{ github.repository }}/commit/${{ needs.resolve.outputs.sha }}#service
concurrency: zipx-deploy-deploy-service-prod
env:
ZIPX_DEPLOY_SHA: ${{ needs.resolve.outputs.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.resolve.outputs.sha }}
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: deploy-service-prod
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: deploy
run: sbt 'service/promote'
deploy-service-stg:
name: deploy service (stg)
runs-on: ubuntu-latest
needs:
- docker-service
- resolve
if: "!cancelled() && needs.resolve.result == 'success' && contains(fromJson(needs.resolve.outputs.targets)['stg'], 'service') && needs.docker-service.result != 'failure'"
environment:
name: staging
url: ${{ github.server_url }}/${{ github.repository }}/commit/${{ needs.resolve.outputs.sha }}#service
concurrency: zipx-deploy-deploy-service-stg
env:
ZIPX_DEPLOY_SHA: ${{ needs.resolve.outputs.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.resolve.outputs.sha }}
fetch-depth: "0"
fetch-tags: "true"
- name: zipx sbt setup
uses: ./.github/actions/zipx-sbt-setup
with:
java-version: "21"
runner-os: ubuntu-latest
cache-key-suffix: deploy-service-stg
node-version: ""
sbt-disk-cache: "false"
cache-mode: restore
cache-epoch: "0.1.0-SNAPSHOT"
- name: deploy
run: sbt 'service/promote'