Hi everyone,
I’m trying to build a high-level tool on top of docker compose using Go.
I’m trying to figure out how to setup a docker compose client in the same manner that docker compose
command does it.
This is my self-contained main.go:
package main
import (
"context"
"flag"
"log"
"path/filepath"
"github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/loader"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
"github.com/docker/compose/v2/pkg/api"
"github.com/docker/compose/v2/pkg/compose"
"github.com/docker/docker/client"
)
func main() {
filePath := "./docker-compose.yaml"
flag.StringVar(&filePath, "path", filePath, "Path to the Compose file")
flag.Parse()
// Get absolute path
filePath, err := filepath.Abs(filePath)
if err != nil {
log.Fatalf("Error getting absolute path for %s: %v", filePath, err)
}
projectName := loader.NormalizeProjectName(
filepath.Base(filepath.Dir(filePath)),
)
// Parse project
ctx := context.Background()
options, err := cli.NewProjectOptions([]string{filePath},
cli.WithOsEnv,
cli.WithDotEnv,
cli.WithName(projectName),
)
if err != nil {
log.Fatalf("Error creating project options: %v", err)
}
project, err := options.LoadProject(ctx)
if err != nil {
log.Fatalf("Error loading project: %v", err)
}
// Initialize docker client
client, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Fatalf("Error initializing docker client: %v", err)
}
// Initialize docker cli
cli, err := command.NewDockerCli(command.WithAPIClient(client))
if err != nil {
log.Fatalf("Error initializing docker cli: %v", err)
}
// Initialize docker cli context
if err := cli.Initialize(flags.NewClientOptions()); err != nil {
log.Fatalf("Error initializing docker cli context: %v", err)
return
}
// Initialize docker-compose backend client
backend := compose.NewComposeService(cli)
if err != nil {
log.Fatalf("Error initializing docker-compose backend: %v", err)
}
optsCreate := api.CreateOptions{
Services: []string{},
Recreate: api.RecreateDiverged,
RecreateDependencies: api.RecreateDiverged,
Inherit: true,
}
optsStart := api.StartOptions{
Project: project,
NavigationMenu: true,
}
optsUp := api.UpOptions{
Create: optsCreate,
Start: optsStart,
}
project, err = project.WithServicesEnabled()
if err != nil {
log.Fatalf("Error enabling services: %v", err)
}
// Start the project
if err := backend.Up(ctx, project, optsUp); err != nil {
log.Fatalf("Error starting project: %v", err)
}
}
This is the docker-compose.yaml
file in the same directory (“…/compose-go/”):
services:
example-service:
image: ubuntu:20.04
command: ["echo", "Hello, World!"]
volumes:
- example-volume:/example
networks:
- example-network
volumes:
example-volume:
name: example-volume
networks:
example-network:
name: example-network
driver: bridge
When I execute go run main.go
, I get the following error:
[+] Running 1/1
✔ Container compose-go-example-service-1 Created 0.1s
2025/01/27 12:59:47 Error starting project: service "example-service" has no container to start
exit status 1
When I execute it again, I get a different error:
[+] Running 0/1
⠋ Container compose-go-example-service-1 Creating 0.0s
2025/01/27 13:00:30 Error starting project: Error response from daemon: Conflict. The container name "/compose-go-example-se
rvice-1" is already in use by container "2f26df711ed6d702392caf6a97b64e64f5b747aefd39685988fce5364c58fcf2". You have to remo
ve (or rename) that container to be able to reuse that name.
exit status 1
So it seems that backend.Up
successfully creates the container, but doesn’t “recognize” it as its own afterwards.
Does anyone know what I’m doing wrong here?