"See this docker container? I wish I could run another one just like it, but I'll be damned if I'm going to type all those command-line switches manually!"
This is what runlike does. You give it a docker container, it outputs the command line necessary to run another one just like it, along with all those pesky options (ports, links, volumes, ...). It's a real time saver for those that normally deploy their docker containers via some CM tool like Ansible/Chef and then find themselves needing to manually re-run some container.
runlike 2.0 is a rewrite in Go. The command line is the same as 1.x; the
output is more complete and safe to run. It covers every docker run
option of Docker CLI v29.9.3 (see Supported options).
Install
- Prebuilt binaries for Linux, macOS (Apple Silicon) and Windows: download
runlike-linux-amd64, runlike-linux-arm64, runlike-darwin-arm64,
runlike-windows-amd64.exe, runlike-windows-arm64.exe; checksums in
SHA256SUMS), rename it to runlike (or runlike.exe), make it
executable, and put it on your PATH. runlike calls the docker command, so the
Docker CLI must be installed too.
- Homebrew on macOS (Apple Silicon) and Linux:
brew install lavie/tap/runlike
- apt on Debian and Ubuntu (amd64, arm64), from the signed repository
sudo install -d -m 0755 /etc/apt/keyrings
sudo curl -fsSLo /etc/apt/keyrings/runlike.asc https://lavie.github.io/apt/runlike.asc
printf 'Types: deb\nURIs: https://lavie.github.io/apt\nSuites: stable\nComponents: main\nSigned-By: /etc/apt/keyrings/runlike.asc\n' \
| sudo tee /etc/apt/sources.list.d/runlike.sources >/dev/null
Then sudo apt-get update && sudo apt-get install runlike; apt-get
upgrade keeps it current. Without the repository: download
runlike_X.Y.Z_amd64.deb (or _arm64.deb) from the releases page and
run sudo apt install ./runlike_*.deb. The package suggests the Docker
CLI but does not install it: use docker-ce-cli, docker.io or a remote
context.
- With Go (1.24 or later):
go install github.com/lavie/runlike/v2/cmd/runlike@latest
- Docker image, nothing to install: assaflavie/runlike
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
assaflavie/runlike YOUR-CONTAINER
Or as an alias in ~/.profile or ~/.bashrc:
alias runlike="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock assaflavie/runlike"
Usage
runlike [OPTIONS] CONTAINER
This prints the docker run command that creates a container just like
CONTAINER. runlike runs docker container inspect (and docker image
inspect for its image) through your docker command, so Docker contexts,
DOCKER_CONTEXT and DOCKER_HOST work as usual.
| Option | What it does |
|---|---|
| -p, --pretty | One option per line |
| --no-name | Leave out --name, so the copy does not collide with the original |
| -l, --no-labels | Leave out --label |
| --use-volume-id | Keep the generated names of anonymous volumes, so the copy reuses their data |
| -s, --stdin | Read docker inspect JSON from stdin instead of asking Docker |
| --shell SHELL | Quote for posix (default: sh, bash, zsh), powershell (7.3 or later) or cmd |
| --help | Show the options |
For example:
$ docker run -d --name web --restart=unless-stopped -p 8080:80 \ -e 'GREETING=hello world' -v data:/data --network appnet \ --network-alias web --memory 256m --label app=demo \ alpine:3.20 sleep 3600
$ runlike -p web docker run \ --name=web \ '--env=GREETING=hello world' \ --detach \ --network=appnet \ --network-alias=web \ --publish=8080:80 \ --volume=data:/data \ --memory=256m \ --restart=unless-stopped \ --label=app=demo \ alpine:3.20 \ sleep 3600
Running the output
To run the copy in one step, use eval with double quotes:
eval "$(runlike --no-name web)"
Do not use a bare $(runlike web), as the 1.x README suggested. The shell
splits that output on spaces and does not remove quotes, so a value such as
GREETING=hello world reaches Docker as two broken arguments, and the copy
runs with wrong settings or fails. eval makes the shell parse the line the
way it would if you typed it.
runlike quotes every value it prints, including values that someone with
control over the container set (environment, labels, commands). The quoting
is fuzz-tested so that running the line never runs anything else. In
PowerShell, run runlike --shell powershell web and paste the line; in
cmd.exe, use --shell cmd.
From docker inspect output, for example a saved file:
docker inspect web | runlike --stdin
Warnings
When the command cannot recreate something exactly, runlike prints the
command anyway, and a line starting with runlike: warning: on stderr that
says what is off and what to do. Warnings never end up in the command, so
eval "$(runlike X)" still works. The common ones:
- MAC address on a further network. Docker says whether a MAC address was
mac-address=... to that network's --network if you had pinned one.
- Networks left out. Some network settings cannot be given to
docker run.
docker network connect commands to run after
creating the copy.
- Image moved. The copy runs the image named by the container's tag. If
- Image not found. If the tag is gone, runlike compares with the
docker run will pull the tag. If neither
exists, there is no image data (see --stdin below).
- No image data (
--stdin, or the image is gone): see
- The items under Known limits below.
What changed from 1.x
The command line is unchanged: the same options, arguments and exit codes,
plus the new --shell. Scripts that call runlike keep working. The output
changed where 1.x was wrong or incomplete:
- Every value is quoted safely. 1.x quoted some values and not others, so
$ in a value could break the command or change it.
- Options are printed as
--flag=value, one argument each (--env=...,
--publish=...), instead of a mix of short and long forms.
- Defaults are left out. A value is printed only when it differs from
json-file, runtime runc, 64 MB
shared memory, ...) or from the image's own value (environment, labels,
working directory, user, entrypoint, health check, ...).
- Daemon defaults are printed when they differ. If your daemon is
local log driver, for
example), runlike prints the value, so the command makes the same container
on any daemon. An explicit json-file on such a daemon is printed too.
- Named volumes are always kept, and mounts keep the form they were made
-v or --mount, with their options). Anonymous volumes the image
declares are no longer printed (#138).
- All networks. The copy joins every network of the original, with
- MAC addresses. runlike prints a MAC address only when you pinned one
--mac-address), running or stopped, so a copy never duplicates the
original's random MAC. 1.x lost a pinned MAC with current Docker, which
no longer returns it by default. For further networks Docker does not say
(see Known limits). On Docker Desktop for Mac, runlike reads the
pinned MAC through Desktop's own daemon socket, which its API proxy does
not hide.
- Entrypoint arguments are kept. 1.x dropped every entrypoint element but
- A moved or deleted image no longer breaks runlike. It warns and carries
- Auto-generated host names are left out, and ports published with
-P
--publish-all, not as the random ports Docker picked.
- Many more options: resource limits, health checks, security options,
Supported options
runlike 2.0 covers all 103 options of docker run in Docker CLI v29.9.3:
| Status | Options |
|---|---|
| Reproduced | 91 |
| Reproduced, tested on saved data (Windows-only, or kernel features the test machines lack) | 4 |
| Folded into other options | 3: --env-file, --label-file, --use-api-socket |
| Not recoverable (client only) | 5: --detach-keys, --help, --pull, --quiet, --sig-proxy |
Every option, with notes: docs/options.md.
Client-only options change only what the docker client does while it
runs. Docker does not store them, so no tool can read them back:
--detach-keys: the key sequence for detaching from an attached container.--help: prints usage.--pull: whether the client pulls the image before creating the container.--quiet: silences the client's pull output.--sig-proxy: whether the attached client forwards signals to the container.
--env-fileand--label-file: the client reads the file, and its entries
--env and --label.
--use-api-socket: Docker stores a socket mount and an environment
--use-api-socket back.
Known limits
A few things can't be copied exactly: Docker doesn't keep them, or
docker run has no flag for them. Here is what to watch for (runlike
says so on stderr where it can tell):
- Seccomp profiles.
--security-opt seccomp=FILEis stored as the
- Exec-form health checks. A health check written as a JSON list in a
HEALTHCHECK CMD ["curl", "-f", "..."]) has no docker run
flag. When the container's check differs from the image's, runlike prints
the same words in shell form (--health-cmd), which runs them through
/bin/sh.
-e NAMEwithout a value. If the variable was empty in the original
NAME alone. docker run -e NAME copies NAME
from your environment when it is set there, so runlike warns.
- The same environment variable set twice. Only the last value counts;
- MAC address on a further network. Docker reports whether a MAC
--stdin data newer
than API 1.51) runlike cannot tell a pinned MAC from a generated one, so
it prints none and does not warn. If you pinned one there, add
mac-address= to that network's --network yourself. The same holds for
the first network when the container joined it by network ID
(--network ) and has since started: Docker then reports no pinned
MAC for it either, so add --mac-address yourself.
- Upper case on extra networks.
docker runlowercases the long
--network=name=...,alias=... form, so upper case in such a network's name
or driver options is lost (with a warning).
--stdinmode has no image data and no daemon to ask: values the image
--entrypoint=
cannot be told from none, containers named in container: targets are
printed by ID, and only Docker's built-in defaults are left out.
- Hyper-V isolation (Windows containers) is tested only on saved inspect
The Python version (1.x)
The Python package runlike on PyPI is frozen at 1.6.3 and gets no more
releases. Its code lives on the
python-1.x branch.
PyPI itself stops at 1.5.4 (1.6.x was never uploaded there); install 1.6.3 from that branch. If you depend on it, fork that
branch or move to runlike 2.0, which takes the same command line.
Use as a Go library
The package github.com/lavie/runlike/v2 turns docker inspect JSON into
an argument list. Quoting for a shell is a separate, last step.
package main
import (
"fmt"
"log"
"os"
"os/exec"
"github.com/lavie/runlike/v2"
)
func main() {
containerJSON, err := exec.Command("docker", "container", "inspect", os.Args[1]).Output()
if err != nil {
log.Fatal(err)
}
// Image: docker image inspect output; Daemon: the daemon's defaults.
// Both may be left empty, as with --stdin.
in := runlike.Inspect{Container: containerJSON}
cmd, err := runlike.Args(in, runlike.Daemon{}, runlike.Options{NoName: true})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%q\n", cmd.Argv()) // ["docker" "run" ...], ready for exec.Command
line, err := runlike.Format(cmd, runlike.FormatOptions{Shell: runlike.Posix, Pretty: true})
if err != nil {
log.Fatal(err)
}
fmt.Println(line)
for _, w := range cmd.Warnings {
// Warnings quote container data: %q keeps escape codes off the terminal.
fmt.Fprintf(os.Stderr, "warning: %q\n", w)
}
}
runlike.Options holds the command-line switches. runlike.Daemon holds
the daemon's defaults, platform, host name and container names, which the
command-line tool reads from docker info and docker version; one value
serves every container on that daemon. runlike.Inspect holds one
container's data: its inspect JSON, its image's, and the MAC pinned on its
first network (inspect it with DOCKER_API_VERSION=1.51,
runlike.MACAPIVersion). See the package documentation for each field.
Development
The source of truth is the docker run reference and behaviour of one
pinned Docker CLI release, now v29.9.3, kept in
spec/v29.9.3/. Tests run against real Docker daemons:
go test ./...: unit tests and saved-data tests; no Docker needed.- Round-trip tests (test/roundtrip/README.md):
docker inspect output.
- Compatibility suite (test/compat/README.md):
- Linux daemons run in Lima VMs
test/lima/up.sh); Windows containers are tested on a Windows Server VM.
See CONTRIBUTING.md. runlike 2.0 is written and maintained by AI coding agents; a human maintainer approves every release.