Skip to main content

Container Targeting

Orchestrate container lifecycle operations -- pull, create, exec, inspect, stop, and remove -- as a DAG of TaskFunc steps using the standard SDK client.

Setup

Container operations use the same client.Docker service as any other SDK call. No special plan options are needed:

plan := orchestrator.NewPlan(client, orchestrator.OnError(orchestrator.Continue))

Building the DAG

Chain container operations with DependsOn to enforce ordering. Independent operations at the same level run in parallel:

pull := plan.TaskFunc("pull-image",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
_, err := c.Docker.Pull(ctx, "_any", gen.DockerPullRequest{
Image: "ubuntu:24.04",
})
if err != nil {
return nil, err
}
return &orchestrator.Result{Changed: true}, nil
},
)

create := plan.TaskFunc("create-container",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
autoStart := true
resp, err := c.Docker.Create(ctx, "_any", gen.DockerCreateRequest{
Image: "ubuntu:24.04",
Name: ptr("my-app"),
AutoStart: &autoStart,
Command: &[]string{"sleep", "600"},
})
if err != nil {
return nil, err
}
r := resp.Data.Results[0]
return &orchestrator.Result{
Changed: true,
Data: map[string]any{"id": r.ID},
}, nil
},
)
create.DependsOn(pull)

Exec

Execute commands inside running containers. Multiple exec tasks that depend on the same create step run in parallel:

execHostname := plan.TaskFunc("exec-hostname",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
resp, err := c.Docker.Exec(ctx, "_any", "my-app",
gen.DockerExecRequest{Command: []string{"hostname"}})
if err != nil {
return nil, err
}
r := resp.Data.Results[0]
return &orchestrator.Result{
Changed: true,
Data: map[string]any{"stdout": r.Stdout},
}, nil
},
)
execHostname.DependsOn(create)

execUname := plan.TaskFunc("exec-uname",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
resp, err := c.Docker.Exec(ctx, "_any", "my-app",
gen.DockerExecRequest{Command: []string{"uname", "-a"}})
if err != nil {
return nil, err
}
r := resp.Data.Results[0]
return &orchestrator.Result{
Changed: true,
Data: map[string]any{"stdout": r.Stdout},
}, nil
},
)
execUname.DependsOn(create)

Cleanup

Use a cleanup task that depends on all operational tasks to ensure the container is removed even when some tasks fail (with OnError(Continue)):

cleanup := plan.TaskFunc("cleanup",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
force := true
_, err := c.Docker.Remove(ctx, "_any", "my-app",
&gen.DeleteNodeContainerDockerByIDParams{Force: &force})
if err != nil {
return nil, err
}
return &orchestrator.Result{Changed: true}, nil
},
)
cleanup.DependsOn(execHostname, execUname)

Example

See examples/sdk/orchestrator/features/container-targeting.go for a complete working example with hooks, error handling, and a deliberately failing task.