Containers for fun and profit!
So you're now officially excited about Docker and containers.
Good! They're an awesome tool to create portable configurations for your software and make sure it works seamlessly across multiple devices.
If you've been around them long enough, though, you're probably using Docker engine's CLI (command line interface) quite often.
Commands like:
docker exec -ti 1x81hs72k2s9 /bin/bash
or
docker start container-name
Are probably all over your shell history.
I had the same problem, and so I made a small hack and uploaded it to my slowly-expanding Useful Snippets repository (which you can totally star if you feel like it :) ):
TomGranot / useful-snippets
Useful things I made/found over time
useful-snippets
In chronological order of addition, a bunch of useful stuff kept here for persistence:
Money time
Specifically, if you go here you'll see the following snippets:
#!/bin/bash
# Get container id of existing container
dgrep(){
docker ps -a | grep "$1" | cut -c1-12
}
# bash into an existing container
dbash() {
docker exec -ti $(dgrep "$1") /bin/bash
}
# sh into an existing container
dsh() {
docker exec -ti $(dgrep "$1") /bin/sh
}
# Execute something in an existing container
dex() {
docker exec -ti $(dgrep "$1") $2
}
# Start an existing container
dstart(){
docker start $(dgrep "$1")
}
# Stop an existing container
dstop(){
docker stop $(dgrep "$1")
}
# Remove an existing container
drm(){
docker rm $(dgrep "$1")
}
# Stop and remove an existing container
dsrm(){
dstop $(dgrep "$1") && drm $(dgrep "$1")
}
# Remove an existing image
dirm(){
docker image remove $(dgrep "$1")
}
# Get logs of an existing container
dlog(){
docker logs $(dgrep "$1")
}
These go inside your shell's configuration file, probably located at ~/.bashrc
if you're on one of the Linux distros and older macs, or ~/.zshrc
if you're on newer macs.
After you put those commands inside your shell configuration file, whenever your shell starts up they will become available for use inside your session - just like regular shell commands.
Shell functions as command aliases
Note that what I did up there was define shell functions that perform specific commands, and take arguments just like regular shell commands.
This goes in contrast to defining shell aliases, like Nick Taylor does here (for the most part), and allows us to pass arguments to our "aliases".
This was a bit of a mouthful. Let's consider, as an example, the dgrep
function I defined earlier:
# Get container id of existing container
dgrep() {
docker ps -a | grep "$1" | cut -c1-12
}
dgrep()
is a shell function that accepts an unlimited amount of arguments (as shell functions do). In order to access the arguments provided to the function inside the function's body, we use the $X
notation, where X
is an integer number.
$0
always refers to the name of the executing command, so if you're running:
my-command first-tom second-tom
And my-command
is defined as
my-command() {
echo $0;
echo $1;
echo $2;
}
Then the output will be:
my-command
1
2
Coming back to our dgrep
command:
# Get container id of existing container
dgrep() {
docker ps -a | grep "$1" | cut -c1-12
}
Note that what it's doing is getting the contents of docker ps
(that is a list of all running containers), then piping the input into grep
, which returns any match to the searched text with the provided argument.
In our case, it's "grepping" for $1
, which is the first argument provided to dgrep
. It finally pipes that output into cut -c1-12
that gets the first 12 characters of the piped text (which is, coincidentally, exactly the length of Docker container IDs).
For example, if you're running:
dgrep tom-container
Then if tom-container
is a running container, docker ps
will return a string containing all the information the Docker engine has about that container.
dgrep
will then pipe it into the cut
command, and print out only the container ID of that container.
But why do I need that?
Excellent question. Remember our example from the beginning of the article, where we used the full container ID to refer to the container?
docker exec -ti 1x81hs72k2s9 /bin/bash
Nobody expects you to remember the full names or the IDs of your containers. We can now re-write the command as:
docker exec -ti $(dgrep substring-in-container-name) /bin/bash
Where $()
is a subshell - a special command that executes whatever is provided to it, and sends out the output as text before running the rest of the command.
In our case, $(dgrep substring-in-container-name)
will find us a container ID based on some substring in the name (like my
in my-very-long-and-hard-to-remember-container-name
), and pass it to the docker exec -ti CONTAINER-ID /bin/bash
command as CONTAINER-ID
.
Wait, but that's still kinda long...
You're right! "Bashing" into a container (i.e. running bash
inside that container interactively, which is basically "opening" a terminal to that container) is something we do quite often to debug long-running containers, and to do other fun filesystem shenanigans (ask me about that if you'd like to know more!).
In comes dbash()
:
# bash into an existing container
dbash() {
docker exec -ti $(dgrep "$1") /bin/bash
}
As you can see, dbash
is calling the previously mentioned dgrep
in a subshell, and passes that subshell the argument provided to dbash
- namely a substring of the relevant container.
This eventually lets us do something like:
dbash tom
And get a terminal to the first container on the list of running containers that has tom
in its human-readable name.
Pretty neat, right?
Conclusion
Containers are fun. And so is working with the shell!
I literally just made a really nice hack using Docker for an open-source project I'm working on with my good friend, Federico. Feel free to take a look if you're into container wizardry!
Side note
An observant reader would note that I can save one pipe by using Docker command formatting. That reader would be correct, but I think my syntax is simpler and easier to explain to beginners, so I stuck with it for this tutorial (and, honestly, for my own ~/.zshrc
as well).
Top comments (5)
Nice!
I had to make a minor change because I already have a function configured that redefines "docker ps -a" to sort and color results, which broke most of the functions. Switching the degree function to docker ps --all resolved the issue.
Do share! I kinda delegated coloring the shell to grc and starship, to be honest.
Oh-my-zsh is legit amazing, btw.
I used the syntax from this Stack Overflow post:
stackoverflow.com/a/52908761/428945
I personally dislike such approach. Bash complete + reverse search for often used commands makes its job well. So you don't need remember you shortcuts, which may be outdated, or just not available on remote machine.
Not having dbash on remotes have indeed bummed me out many times. One possible solution with machines that you manage yourself is having those shorthands backed up o git, then loading them in the machines that you manage like you do on your local one.
But that's not always possible, so what happens in practice is that I know most often-used commands by heart, and only use the shortcodes on my machine. So, different tastes I guess;)