Last active
February 3, 2025 10:17
-
-
Save maggie44/98d870da0961b61c32f6e55407697f76 to your computer and use it in GitHub Desktop.
Example: Running `docker compose up` in Golang
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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