captain: turning a git commit into a tagged Docker image
Captain - Convert your Git workflow to Docker :whale: containers
At a glance
- What is it?
- captain reads a captain.yaml file in your repository, builds the Dockerfiles it describes, runs the test commands you define, and pushes the images to a registry with tags derived from the git state. The idea is that a commit maps to an image, and a clean tree produces a commit tag and a branch tag that a deployment system can pull.
- Who is it for?
- captain fits a team whose deployment story is git branches and tags, and who want a commit to correspond to a specific image without writing the tagging logic in a shell script or a CI pipeline. It does not fit a team that has standardised on a general CI system that already does tagging, testing and promotion, because captain is a small tool for one specific link in that chain.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 31 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A commit maps to an image
Captain converts a git workflow into Docker containers, and the README states the whole idea in one sentence: define your workflow in the captain.yaml and use captain with your continuous delivery service to create containers for each commit, test them, and push them to your registry only when the tests pass.
The three verbs are the whole interface. captain build builds the Dockerfiles of your repository, captain test runs your tests, and captain push sends the selected images to the remote repository.
The other half of the design is what happens on the consuming side. Once the images exist, you can pull the feature branch you want to test, or create distribution channels such as alpha, beta, and stable using git tags that are propagated to container tags. So a git tag becomes a channel, and the deployment system pulls the channel rather than knowing anything about git.
That is the piece of the design that makes the tool more than a build wrapper. A build script that produces an image named after a commit is easy to write. A build script that produces a stable set of tags, a latest, a branch, a commit, and a release channel derived from git, and that a deployment target can consume by name, is the part that people end up rewriting.
The install is a shell script over the network, and the binary lands in a directory you have to add to your PATH yourself.
curl -sSL https://raw.githubusercontent.com/harbur/captain/v1.1.3/install.sh | bashThe path is added in your shell profile, for example in your .bashrc or your .zshrc.
export PATH=$HOME/.captain/bin:$PATHThe project is MIT licensed, written in Go, and the newest tagged release is v1.1.3 from 2019-04-22, with v1.1.2 from 2017 and v1.1.1 from 2017 before it, although the repository itself was pushed to as recently as 2026-09-23.
The captain.yaml file, and its four stages
Captain configures itself with sensible values without any pre-configuration, so it works in most cases, and the captain.yml file is there for when it does not. It is a simple YAML file placed in the root directory of the git repository, and captain looks for it and uses it to be configured.
The full example from the README is worth reading in full, because it shows the shape of a project with more than one image.
hello-world:
build: Dockerfile
image: harbur/hello-world
pre:
- echo "Preparing hello-world"
post:
- echo "Finished hello-world"
hello-world-test:
build: Dockerfile.test
image: harbur/hello-world-test
pre:
- echo "Preparing hello-world-test"
post:
- echo "Finished hello-world-test"
test:
- docker run -e NODE_ENV=TEST harbur/hello-world-test node mochaTest
- docker run -e NODE_ENV=TEST harbur/hello-world-test node karmaTest
project-with-build-args:
build: Dockerfile
image: harbur/buildargs
build_arg:
keyname: keyvalueThe top level keys are the images, each one named, and the example shows the three cases you need: a plain image, a test image that carries its own test commands, and an image that needs a build argument.
Within an image there are four stages and they run in a fixed order. The pre commands run as preparation before the compilation. The build key names the Dockerfile to compile, and the build context is the directory where the Dockerfile lives, which is the directory sent to the Docker daemon. The test commands run after the compilation of that image, and the post commands run after the compilation as well. For all three command lists, the rule is the same: if any command fails, captain stops and reports a non-zero exit status.
That failure behaviour is the security property of the whole design. The test list is the gate in front of the push, so an image that fails its tests never reaches the registry, and the non-zero exit is what stops a pipeline.
The build_arg key takes a set of key and value pairs that are passed to docker build as build arguments, which is how you vary one image definition for several environments without duplicating it.
Detecting the Dockerfile and the image name
The auto-detection rules are the part of the configuration that will either save you a file or surprise you, so it is worth reading carefully.
For the build key, when auto-detecting, captain walks the current directory and all subdirectories to locate Dockerfiles of the format Dockerfile or Dockerfile.*, and the build path is reconstructed automatically. The build context is the directory where the Dockerfile is located. If more than one Dockerfile is needed in a specific directory, a suffix can be used to separate them and share the same build context.
build: Dockerfile
build: Dockerfile.custom
build: path/to/file/Dockerfile
build: path/to/file/Dockerfile.customFor the image key, the location of the Dockerfile to be compiled is reconstructed by two rules. A Dockerfile becomes username over parent_dir, and a Dockerfile with a suffix becomes username over parent_dir with the parsed suffix appended. The username is the host's username and the parent directory is the Dockerfile's parent directory name. The parsed suffix is lower-cased to avoid invalid repository names, since repository names support only lowercase letters and digits, with underscore, hyphen, and dot allowed.
image: harbur/hello-worldThe lower-casing rule is a small thing that reveals the intent. The detection is designed to produce a repository name that Docker will accept without the user having to intervene, and the rule that the Dockerfile's directory becomes part of the name is what makes several Dockerfiles in one repository coexist in one namespace.
The global flag that overrides this is -N, or --namespace, which sets the default image namespace and defaults to username, so a machine where the user account name is not the image namespace you want needs that flag or an explicit image key.
The tag lifecycle depends on whether your tree is dirty
The tagging behaviour is the core of the tool, and the README describes it in terms of the state of your working copy.
If you are in a non-git repository, captain tags the built images with latest. If you are in a dirty git repository, it tags them with latest as well. If you are in a pristine git repository, it tags them with latest, the commit id, and the branch name.
That rule is the safety property in the design. A working tree with uncommitted changes produces an image that does not correspond to any commit, so it gets the one tag that is not supposed to mean anything specific, and it cannot be mistaken for a build of a particular commit. A clean tree is reproducible from the commit, so it gets the commit tag and the branch tag that a deployment system can pull by name.
The build command follows the same logic. If your repository has local changes, the containers are only tagged as latest, otherwise they are tagged as latest, the commit id, and the branch name, and the README puts the outcome plainly: your git commit tree is reproduced in your local docker repository.
The push and pull commands have flags that control which of those tags cross the network boundary. By default, push pushes the latest and the branch docker tags. The -B flag, all-branches, pushes all branches on a specific commit instead of just the working branch. The -b flag, branch-tags, turns on the branch tags, and it is on by default. The -c flag, commit-tags, turns on the commit tags, and it is off by default.
So the default push is a branch promotion, and the commit tag is opt in. For a deployment system that pins an exact version, the commit tag is the one that matters, and for a system that promotes a branch, the branch tag is the one that matters. The flags let you choose, and the fact that the commit tag is off by default is a deliberate choice rather than an oversight, since pushing every commit id creates a lot of tags in a registry.
The command surface
The CLI is small and the README documents every command and flag, which is a good sign for a tool whose output other systems depend on.
The build command builds the docker images of your repository, in the order they appear in the file, and it has two flags. The -f, or --force, flag forces a build even if the image is already built, and the -B, or --all-branches, flag builds all branches on a specific commit instead of just the working branch.
The test command executes the commands described in the test section, in the order they appear in the file, and it has no flags of its own, because the test commands are the configuration.
The push command pushes the generated images to the remote registry, and the pull command pulls them back, and both take the same three flags for branch and commit tags.
Then there are the housekeeping commands. self-update updates captain to the last available version, version displays the version, and help provides help for any command, so you type captain help with a path to a command for the full details.
The global flags are -D for debug mode, -h for help, and -N for the default image namespace.
The repository layout matches a small Go command line tool. There is a captain.go entry point, and separate files for the parts that matter: config.go for the YAML configuration, docker.go for the image operations, git.go for the repository state, execute.go for running the command lists, print.go and colors.go for output, and exitstatus.go for the exit code handling. The cmd directory holds the command definitions, and the test directory plus the parallel test files, captain_test.go, config_test.go, docker_test.go, execute_test.go, git_test.go, and print_test.go, show that each of those files is tested on its own.
An old toolchain, and a repository that still moves
Two things about the build and the history are worth knowing before you adopt this.
The first is the toolchain. The go.mod declares the module as github.com/harbur/captain with a go directive of 1.12, and the dependency list is from the same era, with the Docker client from go-dockerclient and the Docker library itself pinned to a 2017 commit, cobra at a 2015 tag, logrus and the fatih colour library at their 2017 versions. The Dockerfile at the root is older still, based on golang:1.6.2, and the watch target in the Makefile runs golang:1.4. The cross-compilation target uses gox to produce 386 and amd64 binaries for Windows, Linux, and Darwin.
None of that prevents the tool from working, because the Go toolchain builds old modules without difficulty, but it does tell you the project was written against the Docker API of that period. If you run a current Docker Engine with a current API version, that is the area most likely to need attention, and it is the first thing to check if the build or push commands misbehave.
The second is the release history. The tagged releases are v1.1.3 from 2019-04-22, v1.1.2 from 2017-11-09, and v1.1.1 from 2017-07-27, and the last push to the repository was on 2026-09-23. So there is activity in the repository and no new tagged release in several years, which is a pattern you have seen before in small tools where the author keeps the code working but does not cut a release for every change.
The install command in the README pins the v1.1.3 tag rather than a branch, so an install from that line gives you the 2019 binary. The self-update command exists to move past it, and the VERSION file in the repository is the marker of what the current source thinks it is.
The Makefile offers the alternative route as well, with a build target that runs docker build on the repository, a b target that does a go install of the cmd/captain package, a deps target that downloads, tidies, vendors, and verifies the modules, and a goconvey target for the test suite.
Editorial conclusion
captain fits a team whose deployment story is git branches and tags, and who want a commit to correspond to a specific image without writing the tagging logic in a shell script or a CI pipeline. It does not fit a team that has standardised on a general CI system that already does tagging, testing and promotion, because captain is a small tool for one specific link in that chain. Verify first that your build environment still has a Docker daemon the tool can reach, that the tag names your deployment system expects match what captain produces, in particular whether you want the commit tag enabled, and that the version you install is one you can still get, since the newest tagged release in the repository is v1.1.3 from 2019 while the repository itself is still receiving commits.
Frequently asked questions
What does captain do for a Git workflow?
It reads a captain.yaml file in your repository, builds the Dockerfiles described there, runs the test commands you define, and pushes the images to a registry with tags derived from the git state. Git tags can be propagated to container tags so that channels such as alpha, beta, and stable become pullable names.
How do I install captain?
With the install script over the network, curl -sSL on the raw install.sh URL for the v1.1.3 tag piped to bash, and then add the ~/.captain/bin directory to your PATH in your .bashrc or .zshrc with export PATH=$HOME/.captain/bin:$PATH.
What tags does captain apply to an image?
In a non-git directory or a dirty git repository, the image is tagged only as latest. In a pristine git repository it is tagged as latest, the commit id, and the branch name, so that the commit tree is reproduced in your local docker repository.
What can go in a captain.yaml file?
The top level keys are the images. Each one can name a build Dockerfile or Dockerfile.suffix, set an image name, list pre commands, list test commands, list post commands, and pass build arguments as key and value pairs. Any failing command in the pre, test, or post list stops captain and reports a non-zero exit status.
Is captain still maintained?
The repository is not archived and was last pushed to on 2026-09-23, but the newest tagged release is v1.1.3 from 2019-04-22. The go.mod still declares go 1.12 and pins the Docker libraries to 2017 versions, so the tool predates the current Docker API.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/harbur-captain)Was this useful?