// Package compose runs docker compose the way a stack was deployed — which is not always // simply "on this host". A stack deployed by an orchestrator running inside its own // container records paths in that container's filesystem, so compose has to run in there. // Both the console and the scheduled updates it writes must invoke it identically, or a // redeploy from one would not match the other's labels. Compose always drives this // machine's own Docker: a stack's files and its containers are on the same box. package compose import ( "context" "os/exec" "path/filepath" ) // Spec is everything needed to run compose for one stack. type Spec struct { Project string // -p Files []string // -f, in order Via string // a container to run compose inside; "" is this host Tool string // "docker" or "podman" } // Cmd builds the command. args are compose's own, e.g. "pull" or "up", "-d". func Cmd(ctx context.Context, s Spec, args ...string) *exec.Cmd { tool := s.Tool if tool == "" { tool = "docker" } base := []string{"compose", "-p", s.Project} for _, f := range s.Files { base = append(base, "-f", f) } if s.Via != "" { // The CLI inside the orchestrator's container is its own `docker`, whatever this host runs. return exec.CommandContext(ctx, tool, append(append([]string{"exec", s.Via, "docker"}, base...), args...)...) } cmd := exec.CommandContext(ctx, tool, append(base, args...)...) if len(s.Files) > 0 { cmd.Dir = filepath.Dir(s.Files[0]) } return cmd }