Skip to content

Instantly share code, notes, and snippets.

@maggie44
Last active February 3, 2025 10:17
Show Gist options
  • Save maggie44/98d870da0961b61c32f6e55407697f76 to your computer and use it in GitHub Desktop.
Save maggie44/98d870da0961b61c32f6e55407697f76 to your computer and use it in GitHub Desktop.
Example: Running `docker compose up` in Golang
// Note: Docker Compose is not a library and not officially supported for use this way. However, a number of us
// have found a need to use it in this way and below is the pooled knowledge of how to do so.
// See https://github.com/docker/compose/issues/12513#issuecomment-2630164655 for more info.
package main
import (
"context"
"fmt"
"log/slog"
"os"
"github.com/compose-spec/compose-go/v2/loader"
"github.com/compose-spec/compose-go/v2/types"
"github.com/compose-spec/compose-go/v2/utils"
"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"
)
const exampleCompose = `
services:
example:
image: nginx:latest # Use the latest Nginx image
container_name: example
environment:
- FOO=${SET_ME} # Use the environment variable SET_ME
- BAR=${AND_ME} # Use the environment variable AND_ME
ports:
- "8080:80" # Map port 80 in the container to port 8080 on the host
post_start:
- command: /bin/sh -c "echo somecontent > /root/file.txt"
networks:
- webnet # Connect the web service to the webnet network
networks:
webnet: # Define a custom network for communication between services
`
func main() {
const projectName = "myprojectname"
composeService, err := createComposeService()
if err != nil {
slog.Error(err.Error())
os.Exit(1)
}
err = up(context.TODO(), composeService, projectName)
if err != nil {
slog.Error(err.Error())
os.Exit(1)
}
err = down(context.TODO(), composeService, projectName)
if err != nil {
slog.Error(err.Error())
os.Exit(1)
}
}
func up(ctx context.Context, composeService api.Service, projectName string) error {
// Set the environment variables
envVars := []string{
"SET_ME=ok",
"AND_ME=thanks",
}
// Convert []string of env vars to map
envVarMap := utils.GetAsEqualsMap(envVars)
configDetails := types.ConfigDetails{
ConfigFiles: []types.ConfigFile{
{
Content: []byte(exampleCompose),
},
},
Environment: envVarMap,
}
// Create a new project from the compose config details
project, err := loader.LoadWithContext(ctx, configDetails,
func(opts *loader.Options) {
// We are passing in our own environment
opts.SkipResolveEnvironment = true
// Set the compose project name
opts.SetProjectName(projectName, true)
})
if err != nil {
return err
}
// Optional: Drops networks/volumes/secrets/configs that are not referenced by active services
project = project.WithoutUnnecessaryResources()
// Show what is about to be started
compiledYAML, err := project.MarshalYAML()
if err != nil {
return err
}
fmt.Println("Compose file to be started:")
fmt.Println(string(compiledYAML))
// Sets the labels required for Docker to recognise that these are Compose services
for i, s := range project.Services {
s.CustomLabels = map[string]string{
api.ProjectLabel: project.Name,
api.ServiceLabel: s.Name,
api.VersionLabel: api.ComposeVersion,
api.OneoffLabel: "False",
}
project.Services[i] = s
}
// Optional: configurations
opts := api.UpOptions{
Create: api.CreateOptions{
RemoveOrphans: true,
},
Start: api.StartOptions{
Project: project,
},
}
// Run the up command
err = composeService.Up(ctx, project, opts)
if err != nil {
return err
}
return nil
}
func down(ctx context.Context, composeService api.Service, projectName string) error {
configDetails := types.ConfigDetails{
ConfigFiles: []types.ConfigFile{
{
Content: []byte(exampleCompose),
},
},
/*
Environment is not necessary, but set if you want to avoid the warnings logging in the console:
WARN[0000] The "SET_ME" variable is not set. Defaulting to a blank string.
WARN[0000] The "AND_ME" variable is not set. Defaulting to a blank string.
*/
// Environment: envVarMap,
}
// Create a new project from the compose config details
project, err := loader.LoadWithContext(ctx, configDetails,
func(opts *loader.Options) {
// We are passing in our own environment
opts.SkipResolveEnvironment = true
// Set the compose project name
opts.SetProjectName(projectName, true)
})
if err != nil {
return err
}
// Optional: Drops networks/volumes/secrets/configs that are not referenced by active services
project = project.WithoutUnnecessaryResources()
// Run the up command
err = composeService.Down(
ctx,
projectName,
api.DownOptions{
Project: project,
RemoveOrphans: true,
Volumes: false,
})
if err != nil {
return err
}
return nil
}
func createComposeService() (api.Service, error) {
// Create a Docker client
cli, err := command.NewDockerCli()
if err != nil {
return nil, err
}
// Initialize the Docker client with the default options
err = cli.Initialize(flags.NewClientOptions())
if err != nil {
return nil, err
}
// Create the compose API service instance with the Docker cli
composeService := compose.NewComposeService(cli)
return composeService, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment