{"id":21489322,"url":"https://github.com/reitzig/container-do","last_synced_at":"2025-07-15T16:31:08.813Z","repository":{"id":39897734,"uuid":"270954852","full_name":"reitzig/container-do","owner":"reitzig","description":"Run project-level CLI tools in containers.","archived":false,"fork":false,"pushed_at":"2025-04-21T02:03:39.000Z","size":160,"stargazers_count":6,"open_issues_count":15,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-01T16:18:48.692Z","etag":null,"topics":["containers","docker","tool"],"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/reitzig.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"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,"zenodo":null}},"created_at":"2020-06-09T09:09:29.000Z","updated_at":"2025-02-01T18:57:07.000Z","dependencies_parsed_at":"2023-11-07T15:34:40.574Z","dependency_job_id":"8b1ffea9-395d-411f-9545-bfb7c77ad457","html_url":"https://github.com/reitzig/container-do","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/reitzig/container-do","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reitzig%2Fcontainer-do","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reitzig%2Fcontainer-do/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reitzig%2Fcontainer-do/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reitzig%2Fcontainer-do/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reitzig","download_url":"https://codeload.github.com/reitzig/container-do/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reitzig%2Fcontainer-do/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265445302,"owners_count":23766445,"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":["containers","docker","tool"],"created_at":"2024-11-23T14:18:16.471Z","updated_at":"2025-07-15T16:31:08.464Z","avatar_url":"https://github.com/reitzig.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![license](https://img.shields.io/github/license/reitzig/container-do.svg)](https://github.com/reitzig/container-do/blob/master/LICENSE)\n[![release](https://img.shields.io/github/release/reitzig/container-do.svg)](https://github.com/reitzig/container-do/releases/latest)\n[![GitHub release date](https://img.shields.io/github/release-date/reitzig/container-do.svg)](https://github.com/reitzig/container-do/releases)\n[![Test](https://github.com/reitzig/container-do/workflows/Tests/badge.svg?branch=master\u0026event=push)](https://github.com/reitzig/container-do/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush++)\n[![CodeQL](https://github.com/reitzig/container-do/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/reitzig/container-do/actions/workflows/codeql-analysis.yml)\n\n# container-do\n\nRun project-level CLI tools in (Linux) containers.\nIn particular,\n\n - use tools not available on your platform,\n - avoid managing version conflicts of tooling,\n - persist and share specific setups, and \n - minimize the blast radius of potential mishaps.\n\nA more eloquent reasoning for using containers as development environment \ncan be found in the documentation of \n    [kudulab/dojo](https://github.com/kudulab/dojo#why-was-dojo-created-dojo-benefits).\n\n### Premise\n\n 1. You have a \"project directory\", \n    which we take to mean a directory which contains\n    all files pertaining to the task at hand, and\n    nothing else.\n 2. You need a certain suite of tools (at certain versions)\n    to perform this task.\n 3. A compatible container image exists for this suite. \n\n\n## Install\n\n[Download](https://github.com/reitzig/container-do/releases/latest) \nthe binary matching your OS and put it on the `PATH`.\n\nAs an alternative, you can compile from the sources like so:\n\n```bash\ngo get github.com/reitzig/container-do/cmd/container-do\n```\n\nFind the binary at `$GOPATH/bin`.\n\n## Use\n\n_Prerequisites:_\n\n - Docker installed and user can run `docker`.\n - Container has `sh`.\n\nThere are three special commands:\n\n - `container-do --help` -- print usage instructions.\n - `container-do --init` -- create config file (template).\n - `container-do --kill` -- kill the configured container (if it exists).\n\nAll other calls will be passed directly to the configured container.\nFor instance:\n\n```bash\ncontainer-do npm install\n```\n\nwill run `npm install` inside the container and, more specifically,\nthrough the default `SHELL` _in_ that container.\n\nBy default, `container-do` will try to stay out of your way and \nallow you to focus on the normal command output.\nHowever, you can enable rather more verbose logging\nby setting environment variable `CONTAINER_DO_LOGGING` to `debug`.\n\n### Config File\n\nAt the very least, you will have to tell `container-do` which image to use.\nCreate a file `ContainerDo.toml` with the following content:\n\n```toml\n[container]\nimage = \"my-image\"\n```\n\nAlternatively, run `container-do --init` to get a full template.\nHere is a full list of the optional values:\n\n - `container.os_flavor` (_Default:_ auto-detect)\n \n   For some commands run in the container, we need to know which flavor of Linux it runs.\n   While we will attempt to detect that automatically, this induces a slight performance\n   over head (and may fail).\n   Set to one of `debian`, `fedora`, `alpine`, `gnu/linux`, or `busybox`.\n\n - `container.name` (_Default:_ `${project_dir}-do`)\n \n - `container.work_dir` (_Default:_ `$WORKDIR`)\n \n   Use to override the working directory defined in the container image.\n   Set to an absolute path.\n \n - `container.mounts`  (_Default:_ `[.:$WORKDIR]` / `[]`)\n \n   Unless the container working directory is `/`,\n   the default is a bind-mount from the host working directory to it.\n   Override with entries using the\n     [Docker `--volume` syntax](https://docs.docker.com/storage/bind-mounts/);\n   unlike `docker`, we will resolve relative host paths.\n   \n   _Note:_ You can also use \n     [named volumes](https://docs.docker.com/storage/volumes/#create-and-manage-volumes)!\n\n - `container.ports`  (_Default:_ `[]`)\n \n   Given a list of \n     [port mapping strings](https://docs.docker.com/engine/reference/run/#expose-incoming-ports), \n   we publish the ports as specified. \n \n - `container.keep_alive` (_Default:_ `15m`)\n \n   The duration to keep the container alive for after the last command was run in it.\n   Set to any value compatible with [Go `time`](https://pkg.go.dev/time?tab=doc#ParseDuration).\n\n - `container.keep_stopped` (_Default:_ `false`)\n \n   By default, we remove the container after it stops.\n   Set to `true` to have the container stick around.\n\n - `container.environment` (_Default:_ none)\n    \n    Set environment variables of the container.  \n    If a value is of the form `\"$VARIABLE_NAME\"`,\n    it will be replaced with the value of the thus named environment variable of the _host_,\n    or the empty string if it is not set.\n\n - `run.setup` -- run once after container creation  \n   `run.before` -- run once before each command  \n   `run.after` -- run once after each (successful) command\n   \n    Run pre-defined shell commands, each section specified by:\n    \n    - `run._.attach` (_Default:_ `false`)\n    \n      Set `true` in order to attach the current shell to the command runs.\n    \n    - `run._.user` (_Default:_ `$USER`)\n    \n      Override the default container user to run the commands.\n       \n    - `run._.script_file` (_Default:_ none)   \n    \n      Set to a script file in (relative to `container.work_dir`).\n      Run before any of the `commands` in the same section.\n      \n    - `run._.commands` (_Default:_ `[]`)\n    \n      Set to a list of shell commands run one after the other,\n      so long as they are successful.\n\n - `copy.setup` -- copy files _into_ the container after its creation, but before `run.setup`.  \n   `copy.before` -- copy files _into_ the container before each command, and before `run.before`.    \n   `copy.after` -- copy files _out_ of the container after each (successful) command, and after `run.after`.\n   \n   _Note:_ Each of these can occur multiple times; include them as `[[copy.setup]]`.\n   \n    - `copy._.files` (_Default:_ none)\n      \n      A list of file names and/or [glob strings](https://en.wikipedia.org/wiki/Glob_(programming));\n      all matching files will be copied.\n      Directories will be copied recursively.\n      \n    - `copy._.to` (_Default:_ `container.work_dir` / `.`)\n      \n      Name of the target file or directory.\n      The target will be a directory unless there is a single source file _and_ \n      the target file name does not end in `/`.\n      If the target directory does not exist, it will be created.\n\n   Relative paths are relative to the working directory (for `copy.setup`, `copy.before`) resp.\n   `container.work_dir` (for `copy.after`).\n\n_Note:_ When errors happen during `run.setup` or `copy.setup`, we can not recover graciously.\nTherefore, we immediately kill the container so `_.setup` will be retried before the next command.\n   \n\n### Examples\n\nExplore some use cases:\n\n - [Java with Maven](examples/java)\n - [LaTeX](examples/latex)\n - [Node.js](examples/node)\n - [Webserver](examples/nginx)\n\n\n## Short ADRs\n\n - _Why containers?_\n   \n   While this is not about running services, containers seem a prudent way\n   to isolate versioned tooling from the host system without too much overhead.\n   Also, the approach eliminates the need for tools specific to a certain ecosystem\n   such as venv, rvm, etc.\n   \n - _Why Go?_\n   \n   Efficient binaries seem prudent here.\n   Also, Go seems to be prevalent in the OCI space.\n   _If_ we were to use `docker/client` or `libpod` as libraries, \n   those are written in Go.\n   \n - _Why TOML?_  \n   \n   Comparing to the most common alternatives, \n   TOML seems to provide a better trade-off between expressiveness, cleanliness and usability \n   than either of INI, JSON, YAML.\n    \n - _Why not use `docker/client` resp. `libpod` as libraries?_\n \n   That would mean higher maintenance debt (security patches etc.) and\n   put the duty of ensuring compatibility with user systems on us.\n\n## Other Tools\n\n - [kudulab/dojo](https://github.com/kudulab/dojo) -- Similar vision. Requires custom images but handles more complex setups.\n - [batect/batect](https://github.com/batect/batect) -- More of a build tool than an environment specification.\n\n\n## Acknowledgements\n\nParts of this project were created during \n    [20% time](https://en.wikipedia.org/wiki/20%25_Project) \ngraciously provided by \n    [codecentric](https://codecentric.de).\nThank you!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freitzig%2Fcontainer-do","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freitzig%2Fcontainer-do","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freitzig%2Fcontainer-do/lists"}