{"id":16921345,"url":"https://github.com/thediveo/morbyd","last_synced_at":"2026-03-06T09:02:41.115Z","repository":{"id":219987663,"uuid":"750445772","full_name":"thediveo/morbyd","owner":"thediveo","description":"Easily build and run throw-away test Docker images and containers, and run commands inside them using this thin layer on top of the standard Docker Go client.","archived":false,"fork":false,"pushed_at":"2025-03-13T01:57:44.000Z","size":703,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-25T12:53:28.627Z","etag":null,"topics":["docker","go","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thediveo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-30T16:56:35.000Z","updated_at":"2025-03-01T20:05:54.000Z","dependencies_parsed_at":"2025-02-14T19:26:50.349Z","dependency_job_id":"5002dd74-8852-450f-93ec-0c23f2d5dba7","html_url":"https://github.com/thediveo/morbyd","commit_stats":null,"previous_names":["thediveo/morbyd"],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thediveo%2Fmorbyd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thediveo%2Fmorbyd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thediveo%2Fmorbyd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thediveo%2Fmorbyd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thediveo","download_url":"https://codeload.github.com/thediveo/morbyd/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248442391,"owners_count":21104175,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","go","testing"],"created_at":"2024-10-13T19:51:23.882Z","updated_at":"2026-03-06T09:02:41.096Z","avatar_url":"https://github.com/thediveo.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg alt=\"lxkns logo\" align=\"right\" width=\"200\" src=\"docs/morbyd.png\"\u003e\n\n# `morbyd`\n\n[![PkgGoDev](https://img.shields.io/badge/-reference-blue?logo=go\u0026logoColor=white\u0026labelColor=505050)](https://pkg.go.dev/github.com/thediveo/morbyd)\n[![License](https://img.shields.io/github/license/thediveo/morbyd)](https://img.shields.io/github/license/thediveo/morbyd)\n![build and test](https://github.com/thediveo/morbyd/actions/workflows/buildandtest.yaml/badge.svg?branch=master)\n![goroutines](https://img.shields.io/badge/go%20routines-not%20leaking-success)\n[![Go Report Card](https://goreportcard.com/badge/github.com/thediveo/morbyd)](https://goreportcard.com/report/github.com/thediveo/morbyd)\n![Coverage](https://img.shields.io/badge/Coverage-97.9%25-brightgreen)\n\n`morbyd` is a thin layer on top of the standard Docker Go client to easily build\nand run throw-away test Docker images and containers. And to easily run commands\ninside these containers.\n\nIn particular, `morbyd` hides the gory details of how to stream the output, and\noptionally input, of container and commands via Dockers API. You just use your\n`io.Writer`s and `io.Reader`s, for instance, to reason about the expected\noutput.\n\nThis module makes heavy use of [option\nfunctions](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis).\nSo you can quickly get a grip on Docker's _slightly_ excessive\nknobs-for-everything API design. `morbyd` neatly groups the many `With...()`\noptions in packages, such as `run` for \"_run_ container\" and `exec` for\n\"container *exec*ute\". This design avoids stuttering option names that would\notherwise clash across different API operations for common configuration\nelements, such as names, labels, and options.\n\n- [Session.BuildImage](https://pkg.go.dev/github.com/thediveo/morbyd#Session.BuildImage):\n  options are in the\n  [build](https://pkg.go.dev/github.com/thediveo/morbyd/build) package, such as\n  [WithDockerfile](https://pkg.go.dev/github.com/thediveo/morbyd/build#WithDockerfile),\n  ...\n- [Session.Run](https://pkg.go.dev/github.com/thediveo/morbyd#Session.Run) a new\n  container: options are in the\n  [run](https://pkg.go.dev/github.com/thediveo/morbyd/run) package, such as\n  [WithCommand](https://pkg.go.dev/github.com/thediveo/morbyd/run#WithCommand), ...\n- [Container.Exec](https://pkg.go.dev/github.com/thediveo/morbyd#Container.Exec)ute\n  a command inside a container: options are in the\n  [exec](https://pkg.go.dev/github.com/thediveo/morbyd/exec) package, such as\n  [WithCombinedOutput](https://pkg.go.dev/github.com/thediveo/morbyd/exec#WithCombinedOutput), ...\n- [Session.CreateNetwork](https://pkg.go.dev/github.com/thediveo/morbyd#Session.CreateNetwork):\n  with options in the [net](https://pkg.go.dev/github.com/thediveo/morbyd/net)\n  and [bridge](https://pkg.go.dev/github.com/thediveo/morbyd/net/bridge),\n  [macvlan](https://pkg.go.dev/github.com/thediveo/morbyd/net/macvlan),\n  [ipvlan](https://pkg.go.dev/github.com/thediveo/morbyd/net/ipvlan) sub\n  packages.\n\nFor devcontainer instructions, please see the [section \"DevContainer\"\nbelow](#devcontainer).\n\n## Features of morbyd\n\n  - testable examples for common tasks to get you quickly up and running. Please\n    see the [package\n    documentation](https://pkg.go.dev/github.com/thediveo/morbyd).\n\n  - supports BuildKit using the `build.WithBuildKit()` configuration option with\n    `Session.BuildImage`. Morbyd follows the Docker API that still at the time\n    of this writing defaults to the (in the words of the API documentation)\n    \"deprecated\" builder V1, so BuildKit needs to be opted in. We _are_ slightly\n    morbyd. \n\n  - option function design with extensive [Go Doc\n    comments](https://tip.golang.org/doc/comment) that IDEs show upon option\n    completion. No more pseudo option function \"callbacks\" that are none the\n    better than passing the original Docker config type verbatim.\n\n    - allows you to add your own (missing?) option functions, as all\n      option-related types are exported.\n\n  - uses the [official Docker Go\n    client](https://pkg.go.dev/github.com/docker/docker/client) in order to\n    benefit from its security fixes, functional upgrades, and all the other nice\n    things to get directly from upstream.\n  \n  - “auto-cleaning” that runs when creating a new test session and again at its\n    end, removing all containers and networks especially tagged using\n    `session.WithAutoCleaning` for the test.\n  \n  - uses `context.Context` throughout the whole module, especially integrating\n    well with testing frameworks (such as\n    [Ginkgo](https://pkg.go.dev/github.com/onsi/ginkgo)) that support automatic\n    unit test context creation.\n\n  - extensive unit tests with large coverage. We even mock the Docker client in\n    order to cover the \"unhappy paths\", also known as \"error handling\". In\n    addition, we run go routine leak checks, courtesy of [Gomega\n    `gleak`](https://onsi.github.io/gomega/#codegleakcode-finding-leaked-goroutines).\n\n## Trivia\n\nThe module name `morbyd` is an amalgation of [\"_Moby_\n(Dock)\"](https://www.docker.com/blog/call-me-moby-dock/) and _morbid_ –\nephemeral – test containers.\n\n## Usage\n\n### Run Container and Pick Up Its Output\n\n```go\npackage main\n\nimport (\n    \"context\"\n\n    \"github.com/thediveo/morbyd\"\n    \"github.com/thediveo/morbyd/exec\"\n    \"github.com/thediveo/morbyd/run\"\n    \"github.com/thediveo/morbyd/session\"\n)\n\nfunc main() {\n    ctx := context.TODO()\n    // note: error handling left out for brevity\n    //\n    // note: enable auto-cleaning of left-over containers and\n    // networks, both when creating the session as well as when\n    // closing the session. Use a unique label either in form of\n    // \"key=\" or \"key=value\".\n    sess, _ := morbyd.NewSession(ctx, session.WithAutoCleaning(\"test.mytest=\"))\n    defer sess.Close(ctx)\n\n    // run a container and copy the container's combined output\n    // of stdout and stderr to our stdout.\n    cntr, _ := sess.Run(ctx, \"busybox\",\n        run.WithCommand(\"/bin/sh\", \"-c\", \"while true; do sleep 1; done\"),\n        run.WithAutoRemove(),\n        run.WithCombinedOutput(os.Stdout))\n\n    // run a command inside the container and wait for this command\n    // to finish.\n    cmd, _ := cntr.Exec(ctx,\n        exec.WithCommand(\"/bin/sh\", \"-c\", \"echo \\\"Hellorld!\\\"\"),\n        exec.WithCombinedOutput(os.Stdout))\n    exitcode, _ := cmd.Wait(ctx)\n}\n```\n\n### Deploy Container and Contact Its Published Service\n\n```go\npackage main\n\nimport (\n    \"context\"\n\n    \"github.com/thediveo/morbyd\"\n    \"github.com/thediveo/morbyd/exec\"\n    \"github.com/thediveo/morbyd/run\"\n    \"github.com/thediveo/morbyd/session\"\n)\n\nfunc main() {\n    ctx := context.TODO()\n    // note: error handling left out for brevity\n    //\n    // note: enable auto-cleaning of left-over containers and\n    // networks, both when creating the session as well as when\n    // closing the session. Use a unique label either in form of\n    // \"key=\" or \"key=value\".\n    sess, _ := morbyd.NewSession(ctx, session.WithAutoCleaning(\"test.mytest=\"))\n    defer sess.Close(ctx)\n\n    cntr, _ := sess.Run(ctx, \"busybox\",\n        run.WithCommand(\"/bin/sh\", \"-c\", `echo \"DOH!\" \u003e index.html \u0026\u0026 httpd -f -p 1234`),\n        run.WithAutoRemove(),\n        run.WithPublishedPort(\"127.0.0.1:1234\"))\n\n    svcAddrPort := container.PublishedPort(\"1234\").Any().UnspecifiedAsLoopback().String()\n    req, _ := http.NewRequest(http.MethodGet, \"http://\"+svcAddrPort+\"/\", nil)\n    resp, _ := http.DefaultClient.Do(req.WithContext(ctx))\n    defer resp.Body.Close()\n    body, _ := io.ReadAll(resp.Body)\n    fmt.Sprintf(\"%s\\n\", body)\n}\n```\n\n### Dealing with Container Output\n\n[safe.Buffer](https://pkg.go.dev/github.com/thediveo/morbyd/safe#Buffer) is the\nconcurrency-safe drop-in sibling to Go's\n[bytes.Buffer](https://pkg.go.dev/bytes#Buffer): it is essential in unit tests\nthat reason about container output without setting off Go's race detector. The\nreason is that container output is handled on background Go routines and\nsimultaneously polling an unguarded `bytes.Buffer` causes a race. All you need\nto do is replace `bytes.Buffer` with `safe.Buffer` (which is just a thin\nmutex'ed wrapper), for instance, in this test leveraging\n[Gomega](https://onsi.github.io/gomega/):\n\n```go\nvar buff safe.Buffer\n\n// run a container that outputs a magic phrase and then\n// keeps sleeping until the container gets terminated.\nExpect(cntr.Exec(ctx,\n  exec.Command(\"/bin/sh\", \"-c\", \"echo \\\"**FOO!**\\\" 1\u003e\u00262; while true; do sleep 1; done\"),\n  exec.WithTTY(),\n  exec.WithCombinedOutput(\n    io.MultiWriter(\n      \u0026buff, timestamper.New(GinkgoWriter))))).To(Succeed())\n\n// ensure we got the magic phrase\nEventually(buff.String).Should(Equal(\"**FOO!**\\r\\n\"))\n```\n\n[timestamper.New](https://pkg.go.dev/github.com/thediveo/morbyd@v0.13.0/timestamper#New)\nreturns a writer object implementing [io.Writer](https://pkg.go.dev/io#Writer)\nthat time stamps each line of output. It has proven useful in debugging tests\ninvolving container output.\n\n## Alternatives\n\nWhy `morbyd` when there are already other much bigger and long-time\nbattle-proven tools for using Docker images and containers in Go tests?\n\n- for years, [@ory/dockertest](https://github.com/ory/dockertest) has served me\n  well. Yet I eventually hit its limitations hard: for instance, dockertest\n  cannot handle Docker's `100 CONTINUE` API protocol upgrades, because of its\n  own proprietary Docker client implementation. However, this functionality is\n  essential in streaming container and command output and input – and thus only\n  allowing diagnosing tests. Such issues are unresponded and unfixed. In\n  addition, having basically to pass functions for configuration of Docker data\n  structures is repeating the job of option functions at each and every\n  dockertest call site.\n- [Testcontainers for Go](https://golang.testcontainers.org/) as a much larger\n  solution with a steep learning curve as well as some automatically installing\n  infrastructure – while I admire this design, it is difficult to understand\n  what _exactly_ is happening. Better keep it simple.\n\n## DevContainer\n\n\u003e [!CAUTION]\n\u003e\n\u003e Do **not** use VSCode's \"~~Dev Containers: Clone Repository in Container\n\u003e Volume~~\" command, as it is utterly broken by design, ignoring\n\u003e `.devcontainer/devcontainer.json`.\n\n1. `git clone https://github.com/thediveo/enumflag`\n2. in VSCode: Ctrl+Shift+P, \"Dev Containers: Open Workspace in Container...\"\n3. select `enumflag.code-workspace` and off you go...\n\n## Supported Go Versions\n\n`morbyd` supports versions of Go that are noted by the [Go release\npolicy](https://golang.org/doc/devel/release.html#policy), that is, major\nversions _N_ and _N_-1 (where _N_ is the current major version).\n\n## Contributing\n\nPlease see [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Copyright and License\n\n`morbyd` is Copyright 2024, 2025 Harald Albrecht, and licensed under the Apache\nLicense, Version 2.0.\n\nThe package `github.com/thediveo/morbyd/run/dockercli` is [Copyright 2013-2017\nDocker, Inc.](https://github.com/moby/moby/blob/v25.0.1/LICENSE) and licensed\nunder the Apache License Version 2.0, with the elements listed below coming from\nthe [github.com/docker/cli](https://github.com/docker/cli) module in order to\nwork around import dependency versioning problems due to `@docker/cli` using a\nmanaged `vendor/` directory, but not providing a `go.mod` and the associated\nguarantees:\n- [opts/mount.go](https://github.com/docker/cli/blob/v25.0.1/opts/mount.go);\n  removed the logrus dependency.\n- [opts/network.go](https://github.com/docker/cli/blob/v25.0.1/opts/network.go)\n- a subset of\n  [cli/compose/types/types.go](https://github.com/docker/cli/blob/v25.0.1/cli/compose/types/types.go):\n  type `ServiceVolumeConfig`, with direct dependency types `ServiceVolumeBind`,\n  `ServiceVolumeVolume`, `ServiceVolumeTmpfs`, and `ServiceVolumeCluster`.\n- [cli/compose/loader/volume.go](https://github.com/docker/cli/blob/v25.0.1/cli/compose/loader/volume.go)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthediveo%2Fmorbyd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthediveo%2Fmorbyd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthediveo%2Fmorbyd/lists"}