Skip to content

Commit eb8e349

Browse files
ostermanclaude
andcommitted
feat(workflow): background container services + with: step vocabulary
Container steps now take a single GitHub-Actions-style `with:` params block selected by `action:` (steps only; container components are unchanged). All action parameters live under `with:`; only provider/container/runtime_auto_start stay top-level. Adds background container services: a `container` step with `background: true` starts a long-running service detached and the workflow continues. Readiness reuses the existing container `healthcheck:` via `container.WaitHealthy` (implicit gate + explicit `wait`/`wait-all`); `cancel` tears it down via `container.Down`; anything still running is auto-torn-down at workflow end. - pkg/background: generic Runner/Handle/Registry seam (shell/atmos runner later) - pkg/workflow: container runner (Up/WaitHealthy/Down) + Start/Wait/WaitAll/Cancel - pkg/runner/step: wait/wait-all/cancel action-step handlers - pkg/schema: with:/background:/for: polymorphic decode + background validation - hooks: decode step `with:` via yaml.v3 so WorkflowStep.UnmarshalYAML fires - docs + blog + roadmap + JSON schemas + runnable examples/background-steps PRD: docs/prd/parallel-workflow-steps.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 26d8844 commit eb8e349

42 files changed

Lines changed: 2581 additions & 390 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/prd/parallel-workflow-steps.md

Lines changed: 412 additions & 0 deletions
Large diffs are not rendered by default.

docs/prd/workflow-step-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Plus **per-step output modes** for controlling how command output is displayed.
4141
1. **Full Gum CLI Parity**: We implement the most useful subset, not every Gum option
4242
2. **External Process Execution**: Step types are native Go, not shelling out to `gum`
4343
3. **Custom Themes per Step**: Steps use the global Atmos theme, not per-step theming
44-
4. **Parallel Step Execution**: Steps execute sequentially (existing behavior)
44+
4. **Parallel Step Execution**: Out of scope for *this* PRD. Concurrent execution shipped separately via the `parallel`/`matrix` control steps — see [`parallel-workflow-steps.md`](./parallel-workflow-steps.md).
4545
5. **Conditional Logic**: No if/else branching in workflows (use shell for this)
4646

4747
## Proposed Configuration
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Background container services
2+
3+
This example shows how to start a long-running container service in the background,
4+
run work against it, and tear it down — all from a workflow.
5+
6+
A container step with `background: true` starts detached and the workflow continues
7+
to the next step. Atmos reuses the existing container lifecycle to supervise it:
8+
9+
- **Readiness** reuses the container `healthcheck:` (under `with:`). When a health
10+
check is configured, Atmos blocks until the service is **healthy** before the next
11+
step (the implicit readiness gate). An explicit `{type: wait, for: [name]}` (or
12+
`{type: wait-all}`) does the same on demand.
13+
- **Teardown** is an explicit `{type: cancel, for: [name]}` step (stop + remove).
14+
Anything still running when the workflow ends is **auto-torn-down** — a service
15+
never exits on its own, so it is never "waited to exit".
16+
17+
> Requires a container runtime (Docker or Podman).
18+
19+
## Run it
20+
21+
```shell
22+
cd examples/background-steps
23+
24+
# Start nginx in the background, wait until healthy, use it, then cancel it.
25+
atmos workflow service -f background
26+
27+
# Start redis + nginx in the background, wait-all, then tear both down.
28+
atmos workflow fanout -f background
29+
```
30+
31+
## How it maps to the syntax
32+
33+
```yaml
34+
- name: cache
35+
type: container
36+
action: run
37+
background: true # start detached, keep going
38+
with: # all container params live under `with:`
39+
image: nginx:alpine
40+
healthcheck: # the readiness gate (reuses the container health check)
41+
test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || exit 1"]
42+
interval: 2s
43+
retries: 10
44+
- name: use-service
45+
type: shell
46+
command: ... # runs only after `cache` is healthy
47+
- type: cancel
48+
for: cache # graceful teardown
49+
```
50+
51+
See the [`parallel`](../parallel-steps) example for the complementary *structured*
52+
concurrency (`parallel`/`matrix`) control steps.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
base_path: "./"
2+
3+
workflows:
4+
base_path: "workflows"
5+
6+
logs:
7+
level: Info
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
workflows:
2+
# Start a long-running container service in the background, run work against it,
3+
# then tear it down. Readiness reuses the container `healthcheck:` — Atmos blocks
4+
# until the service is healthy before the next step runs.
5+
service:
6+
description: Start a background service, use it, then cancel it.
7+
steps:
8+
- name: cache
9+
type: container
10+
action: run
11+
background: true
12+
with:
13+
# A tiny long-running service with a health check. nginx:alpine ships
14+
# busybox `wget`, so the check probes the web root.
15+
image: nginx:alpine
16+
ports:
17+
- { host: 8080, container: 80 }
18+
healthcheck:
19+
test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || exit 1"]
20+
interval: 2s
21+
timeout: 2s
22+
retries: 10
23+
start_period: 2s
24+
25+
# Implicit readiness gate: because `cache` has a health check, this step does
26+
# not run until the service reports healthy.
27+
- name: use-service
28+
type: shell
29+
command: 'echo "service is healthy; doing work against http://localhost:8080"'
30+
31+
# Graceful teardown (stop + remove). Omit this and Atmos auto-tears-down the
32+
# service when the workflow ends — a service is never "waited to exit".
33+
- type: cancel
34+
for: cache
35+
36+
# Start several background services and gate on all of them with `wait-all`.
37+
fanout:
38+
description: Start two services in the background, wait for all, then tear down.
39+
steps:
40+
- name: redis
41+
type: container
42+
action: run
43+
background: true
44+
with:
45+
image: redis:7-alpine
46+
ports: [{ host: 6379, container: 6379 }]
47+
healthcheck:
48+
test: ["CMD", "redis-cli", "ping"]
49+
interval: 2s
50+
retries: 10
51+
52+
- name: web
53+
type: container
54+
action: run
55+
background: true
56+
with:
57+
image: nginx:alpine
58+
ports: [{ host: 8081, container: 80 }]
59+
healthcheck:
60+
test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || exit 1"]
61+
interval: 2s
62+
retries: 10
63+
64+
- type: wait-all
65+
66+
- name: smoke
67+
type: shell
68+
command: 'echo "redis and web are both healthy"'
69+
70+
- type: cancel
71+
for: [redis, web]

examples/container-step/atmos.yaml

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ commands:
5252
- name: hello
5353
type: container
5454
action: run
55-
run:
55+
with:
5656
image: alpine:latest
5757
command: echo "hello from a custom command container"
5858

@@ -62,7 +62,7 @@ commands:
6262
- name: build
6363
type: container
6464
action: build
65-
build:
65+
with:
6666
context: .
6767
dockerfile: Dockerfile
6868
tags:
@@ -73,13 +73,13 @@ commands:
7373
- name: inspect
7474
type: container
7575
action: inspect
76-
inspect:
76+
with:
7777
image: "{{ .steps.build.outputs.image }}"
7878

7979
- name: run
8080
type: container
8181
action: run
82-
run:
82+
with:
8383
image: "{{ .steps.build.outputs.image }}"
8484
command: |
8585
/usr/local/bin/example
@@ -91,7 +91,7 @@ commands:
9191
- name: build
9292
type: container
9393
action: build
94-
build:
94+
with:
9595
context: .
9696
dockerfile: Dockerfile
9797
tags:
@@ -102,7 +102,7 @@ commands:
102102
- name: push
103103
type: container
104104
action: push
105-
push:
105+
with:
106106
image: "{{ .steps.build.outputs.image }}"
107107
tags:
108108
- localhost:5000/atmos-container-step:local
@@ -113,27 +113,29 @@ commands:
113113
- name: run
114114
type: container
115115
action: run
116-
run:
116+
with:
117117
image: "{{ .steps.push.outputs.image }}"
118118
command: |
119119
/usr/local/bin/example
120120
uname -a
121121
122122
- name: flat-run
123-
description: Run using the backward-compatible flat run shorthand
123+
description: Run a single command using the with params block
124124
steps:
125125
- name: hello
126126
type: container
127-
image: alpine:latest
128-
command: echo "hello from flat run shorthand"
127+
action: run
128+
with:
129+
image: alpine:latest
130+
command: echo "hello from with params"
129131

130132
- name: workspace
131133
description: Show the mounted workspace from a container
132134
steps:
133135
- name: workspace
134136
type: container
135137
action: run
136-
run:
138+
with:
137139
image: alpine:latest
138140
command: |
139141
uname -a
@@ -148,6 +150,6 @@ commands:
148150
env:
149151
EXAMPLE_MESSAGE: "hello from env"
150152
action: run
151-
run:
153+
with:
152154
image: alpine:latest
153155
command: echo "$EXAMPLE_MESSAGE"

examples/container-step/workflows/container-step.yaml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ workflows:
4242
- name: hello
4343
type: container
4444
action: run
45-
run:
45+
with:
4646
image: alpine:latest
4747
command: echo "hello from a workflow container"
4848

@@ -52,7 +52,7 @@ workflows:
5252
- name: build
5353
type: container
5454
action: build
55-
build:
55+
with:
5656
context: .
5757
dockerfile: Dockerfile
5858
tags:
@@ -63,7 +63,7 @@ workflows:
6363
- name: run
6464
type: container
6565
action: run
66-
run:
66+
with:
6767
image: "{{ .steps.build.outputs.image }}"
6868
command: |
6969
/usr/local/bin/example
@@ -75,8 +75,8 @@ workflows:
7575
- name: build
7676
type: container
7777
action: build
78-
build:
79-
provider: docker
78+
provider: docker
79+
with:
8080
engine: buildx
8181
tags:
8282
- atmos-container-step:bake
@@ -92,7 +92,7 @@ workflows:
9292
- name: run
9393
type: container
9494
action: run
95-
run:
95+
with:
9696
image: "{{ .steps.build.outputs.image }}"
9797
command: /usr/local/bin/example
9898

@@ -102,7 +102,7 @@ workflows:
102102
- name: build
103103
type: container
104104
action: build
105-
build:
105+
with:
106106
context: .
107107
dockerfile: Dockerfile
108108
tags:
@@ -113,7 +113,7 @@ workflows:
113113
- name: push
114114
type: container
115115
action: push
116-
push:
116+
with:
117117
image: "{{ .steps.build.outputs.image }}"
118118
tags:
119119
- localhost:5000/atmos-container-step:local
@@ -124,7 +124,7 @@ workflows:
124124
- name: run
125125
type: container
126126
action: run
127-
run:
127+
with:
128128
image: "{{ .steps.push.outputs.image }}"
129129
command: |
130130
/usr/local/bin/example
@@ -141,7 +141,7 @@ workflows:
141141
type: container
142142
action: build
143143
identity: dev-admin
144-
build:
144+
with:
145145
context: .
146146
dockerfile: Dockerfile
147147
tags:
@@ -153,7 +153,7 @@ workflows:
153153
type: container
154154
action: push
155155
identity: dev-admin
156-
push:
156+
with:
157157
image: "{{ .steps.build.outputs.image }}"
158158
outputs:
159159
image: "{{ .metadata.image }}"
@@ -165,7 +165,7 @@ workflows:
165165
- name: workspace
166166
type: container
167167
action: run
168-
run:
168+
with:
169169
image: alpine:latest
170170
command: |
171171
uname -a
@@ -184,7 +184,7 @@ workflows:
184184
env:
185185
STEP_MESSAGE: "hello from step env"
186186
action: run
187-
run:
187+
with:
188188
image: alpine:latest
189189
command: |
190190
echo "$WORKFLOW_MESSAGE"
@@ -196,7 +196,7 @@ workflows:
196196
- name: check
197197
type: container
198198
action: run
199-
run:
199+
with:
200200
image: alpine:latest
201201
command: |
202202
echo "simulated check failed" >&2
@@ -210,6 +210,6 @@ workflows:
210210
tty: true
211211
interactive: true
212212
action: run
213-
run:
213+
with:
214214
image: alpine:latest
215215
command: /bin/sh

0 commit comments

Comments
 (0)