This article is also available on Medium.
Introduction
I am a web developer and I use docker-compose in all of my projects. Sometimes it is just one container for the frontend part of the app, sometimes it is the whole stack: client, api, database, proxy server.
I recently started using WSL2 as the backend of the Docker for Windows service, and for performance reasons I keep my projects on the Ubuntu VM. This means that I do the command line work in Ubuntu's terminal, which uses Bash.
If you are curious as to why I am using Docker everywhere, check out my article about the pros of using it as a development environment.
9 reasons why you should use Docker as a development environment | by Dániel Gál | Oct, 2020 | Medium
Dániel Gál ・ ・ 5 min read
Medium
The problem
Given the circumstances, I have to type many long commands to start up, manage and stop my stack. I would guess that I use some form of docker-compose
at least 50 times a day, with the length of the command being 20-60 characters depending on the project. The history feature of Bash is not perfect either, pressing the up and down arrows until you find the command you are looking for requires more attention than you might expect.
Photo Soumil Kumar by on Pexels
The goal
I would like to substitute most of the commands I use by 2 or 3 character long mini-commands. I should choose short commands that you are not likely to use. I chose du
, dud
, ds
, dd
, da
, dss
and dus
.
Note that I'm not going to define a shortcut to every Docker command, as that would be pointless. My goal is that I can use a mini-command at least 8 out of 10 times.
But there is a catch. There are some projects, where we have two different compose files: one for development and one for production. In this case, I would like to use the dev compose file.
This requires a -f docker-compose.dev.yml
option.
The commands I will define bash functions for:
-
docker-compose up
— starting the containers -
docker-compose up -d
— starting the containers in detached mode -
docker-compose stop
— stopping the containers -
docker-compose exec <servicename> <command>
— attaching to a running service with a shell of our choice -
docker-compose up <servicename> -d
— starting a specific service in detached mode -
docker-compose stop <servicename>
— stopping a specific service
As you can see some of these functions will have parameters, which will have default values.
The solution
I am going to define my functions in the home directory, in a file named .bash_functions
. This file should be sourced at the beginning of the terminal session, so I extend my .bashrc
file with the following code:
if [ -f ~/.bash_functions ]; then
. ~/.bash_functions
fi
Now onto the .bash_functions
file.
I start with the first part of the command. We need to determine if we need the -f
option or not.
function set-compose-command {
COMPOSE_COMMAND="docker-compose"
if [ -f docker-compose.dev.yml ]
then
COMPOSE_COMMAND="docker-compose -f docker-compose.dev.yml"
fi
}
I will not call this function manually, however it will be executed at the beginning of every other function. It will set the COMPOSE_COMMAND
variable in our current shell instance so the other functions just need to attach their specific task at the end. The logic is simple: if the docker-compose.dev.yml
file exists, apply the -f
option with the filename.
Next, I define 4 shortcuts that do not require parameters.
function dud {
set-compose-command
sh -c "$COMPOSE_COMMAND up -d"
}
function du {
set-compose-command
sh -c "$COMPOSE_COMMAND up"
}
function dd {
set-compose-command
sh -c "$COMPOSE_COMMAND down"
}
function ds {
set-compose-command
sh -c "$COMPOSE_COMMAND stop"
}
The next two functions require one argument each. Without an argument, they will fail.
# Stop service
function dss {
if [ -z $1 ]; then
echo "Please specify a service."
return 1
fi
set-compose-command
sh -c "$COMPOSE_COMMAND stop $1"
}
# Start service
function dus {
if [ -z $1 ]; then
echo "Please specify a service."
return 1
fi
set-compose-command
sh -c "$COMPOSE_COMMAND up -d $1"
}
The functions check if the user supplied at least one argument, and exit with a failing status if not.
The last function has two parameters, both of which have default values.
function da {
local service=${1:-dev}
local shell=${2:-sh}
set-compose-command
sh -c "$COMPOSE_COMMAND exec $service $shell"
}
The reason for the default values is that I attach to a sh
shell in the container 90% of the time (I mostly use alpine images and they don't have bash
), and if I only have one service in my compose file, I will most likely name it dev
, hence the default service name.
Conclusion
My docker-compose workflow is now faster and also more comfortable. No more typing the same long commands over and over again.
This is me starting up an environment, making some changes inside and then stopping it when I'm finished:
den@workstation:~/work/myproject$ dud
Starting myproject_myservice_1 ... done
den@workstation:~/work/myproject$ da myservice
/usr/src/myproject # touch somefile.txt
/usr/src/myproject # ^D
den@workstation:~/work/myproject$ ds
Stopping myproject_myservice_1 ... done
den@workstation:~/work/myproject$
Keep in mind that you might not need the exact functions I did, or you might need more. It would be best if you paid some attention to which commands you use most often in your workflow, and define functions for those.
You also don't have to be a bash expert or even an intermediate level script developer to write your custom functionalities. There are plenty of resources to help you, as long as you are not new to programming and know what to search for.
I hope you have a nice day. Work smarter, not harder.
If you're curious about Docker and want to try it out in development or in production, check out these articles to get started:
Top comments (1)
Great process, I love a good bash function.