Where I'm Coming From
I run MacOS for work and Ubuntu for fun. Unix-y stuff has always appealed to me because of its composability and extensibility.
I use Bash. Always have. It has plenty of features and is extensible. I used Zsh for a while, but found my way back to Bash after a time because it was important to me for my shell to match the script language I use (and I didn't grok Zsh scripting).
Our team at work has recently started a process of migration to an off-premises vendor for hosting. Part of our deployment strategy involves deploying our services and applications to Docker which I've began learning mostly in anger. There are new paradigms, commands, subcommands with switches and all kinds of extras that I've never had to think about before. And I've had to think about deployment and management of services more than ever before. DevOps has finally caught me.
In this post, I don't describe what Docker is, how to build an image, etc. This is strictly about setting up something useful for learning and running the basic and (for me) most ran Docker commands quickly and easily.
Why do this?
Developers have to know a lot about their tools. Sometimes we find ourselves running the same things over and over. Thankfully, modern shells have features that let us combine multiple commands into easy to remember mnemonics that are short and quick to type. Bash in particular has functions and aliases that can be defined in your profile to lighten the cognitive load of learning all these commands.
I've started gathering the Docker bits into something that's usable for me and I'm sharing them here with you. What follows is a collection of Bash functions and aliases that I've found useful (that is, when I read my .bash_history
and see the same command run more than 10 times) in working with Docker.
Above All Else, Be Aware of Inconsistency
I learned quickly that docker
and docker-compose
are similar, but at the same time very different. They both share a number of subcommands with the same name. But be warned that the similarly named subcommands don't necessarily support the same options. The evolution of Docker has been exciting since it took the world by storm, but it's come at the cost of consistency in its commands.
Function vs Alias
Functions and aliases in Bash are similar on the surface; you make a mnemonic for a long command. They are very different from each other though and it's important to understand why. See here for a quick explanation of the difference between a Bash function and alias. If you've ever got a choice between defining an alias vs a function, the Bash manual says:
For almost every purpose, shell functions are preferred over aliases.
Naming Functions and Aliases
Naming is the hardest thing in the world for us as developers. When I am thinking about what to name a shell function, my checklist is:
- Length: has to be short (< 20 characters if possible).
- Descriptive: has to concisely describe the action taken.
The Docker functions below all start with the letter d
(or dc
for docker-compose
commands) followed by the sometimes shortened subcommand followed by any other information. Normally I join compound words with an underscore but for the Docker functions I did not.
Aliases should always be shorter names than a function. You can make them longer if you wish, but ~90% of the time I make them shorter.
Function Parameter Checking
Shell functions can have parameters passed to them. I'll often have a check at the beginning of the functions I write that looks like this:
if [ $# -eq 0 ]; then
echo 'Usage: $FUNCNAME ARG [ARG ...]'
return 1
fi
The if
test is of course dependent on what the function needs. This snippet says "if no arguments are passed to the function, print out a usage message and return 1". The return code is important if you're using the function in another function or script. The $FUNCNAME
variable is an internal Bash variable (reference below) that prints out the name of the currently running function.
How I Use Functions
Shell into a Container
Sometimes things don't work as you had expected and you have to take a closer look at a container you've built to see what's going on. You'll need to do that with a shell (or as I've heard it said many times before, "shell into" the container). Docker's exec
command will run a command. Since I want to start a shell in a specified container, the function below simplifies that for me:
# Run a bash shell in the specified container.
#
dexbash() {
if [ $# -ne 1 ]; then
echo "Usage: $FUNCNAME CONTAINER_ID"
return 1
fi
docker exec -it $1 /bin/bash
}
alias deb='dexbash'
This should be read as 'docker exec bash'. The deb
alias shortens this to a nice 3-letter invocation. Why not name the function deb
? You could. This is your shell and you're free to do what you want.
When you're running multiple containers with docker-compose
there's an exec
command for it too, but be sure to leave off the -it
options.
# Run a bash shell in the specified container (with docker-compose).
#
dcexbash() {
if [ $# -ne 1 ]; then
echo "Usage: $FUNCNAME CONTAINER_ID"
return 1
fi
docker-compose exec $1 /bin/bash
}
alias dceb='dcexbash'
Once you're in you can look for files, run probing commands, etc. just as you would in a normal shell. Of course, some of your favorite tools will be missing since this is a Docker container and it has to be kept small.
Building and Tagging
Sometimes you want to build and sometimes you want to build and tag. This is an example of how to make your shell functions smart and demonstrates the power functions have over aliases:
# Runs Docker build and tag it with the given name.
#
dbt() {
if [ $# -lt 1 ]; then
echo "Usage $FUNCNAME DIRNAME [TAGNAME ...]"
return 1
fi
ARGS="$1"
shift
if [ $# -ge 2 ]; then
ARGS="$ARGS -t $@"
fi
docker build $ARGS
}
This will build or build and tag (with multiple tags) your container.
Just Some Aliases
Remember, aliases are simply renamed commands with some flags. They take no parameters like functions do but they will append any flags and arguments you add to them on the command line. These are some aliases of my most-used Docker commands.
alias datt='docker attach'
alias dcb='docker-compose build'
alias dclogs='docker-compose logs'
alias dcu='docker-compose up'
alias ddiff='docker diff'
alias deb='dexbash'
alias dimg='docker images'
alias dins='docker inspect'
alias dps='docker ps'
alias drm='docker rm'
alias drmi='docker rmi'
alias drun='docker run'
alias dstart='docker start'
alias dstop='docker stop'
A Few (More) of My Favorite s/Things/Aliases/
I noted that shell functions are preferable over shell aliases, but sometimes an alias is the right thing for the job. Take this gem for example:
# Credit to <https://gist.github.com/bastman/5b57ddb3c11942094f8d0a97d461b430#remove-docker-images>
alias dclimg='docker rmi $(docker images --filter "dangling=true" -q --no-trunc)'
Imagine you're working on a Docker image and you're building it over and over. What you may not realize as a beginner is that the container that was previously built doesn't simply get overridden! It's still on your hard drive taking up space! When you run a docker images
(or dimg
as above if you noticed) you'll see images without a name. This little guy will clean all of those up for you in 1 command!
Another thing I often will do is specify volumes where configuration files go locally so I can edit them quickly and easily. Depending on what I'm working on I may need to restart a container. This one below is a beaut for that:
# Credit to <https://stackoverflow.com/a/21928864/37776>
alias drestartf='docker start $(docker ps -ql) && docker attach $(docker ps -ql)'
Conclusion
Bash is clearly my favorite shell, and not just for aliases and functions. Other shells have aliases and functions too and these can all be adapted for them. I hope you find these useful if you're using the command line when working with Docker.
Top comments (5)
This is great. Now, how would one go about bash-completing the multi-level command aliases and functions?
Maybe a customization to bash-completion? I think that's a little heavy though. Mostly because aliases and functions are short and are meant to be short. I don't need the shell to remember it for me; I taught the shell what I wanted to do with little effort for both of us (me and the shell).
With Docker (e.g. your
deb
alias), you would want to [tab][tab] container names and image ids, would you not?Sure, that'd be great! But again, that's more effort than the beginning Docker user might do. The expert Bash user could make that happen. Again, bash-completion would be the right thing here, not these aliases and functions.
Where this post was coming from was my work on a recent sprint our team was doing to prove a concept with our web ISO. I had to learn how to use Docker and get my work done. There wasn't time for me to go into doing much more than what I did and be able to get my work done on time and this was a reasonable compromise for me.
Docker has bash completion already, thankfully. The trick is to get the aliases to ride the coat tails of the original functions for bash completion. It turns out it isn't that hard to coax them. If you're interested, here is how I figured it out: stackoverflow.com/questions/477816...