Scenario: local development with Docker¶
We will continue the previous scenario (Scenario: working with micro-services) by Dockerizing the two services. If you haven’t done the steps of the previous scenario, run this to get started:
cd /tmp
git clone git@github.com:mnieber/dodo-commands-tutorial.git
# Copy the end state of part 1 of the tutorial
cp -rf ./dodo-commands-tutorial/part1/after ./tutorial
# Create and activate a dodo environment for our project
cd ./tutorial
$(dodo env --init tutorial)
Running the docker-compose file¶
Our services come with Docker files that we have not used so far.
cat /tmp/tutorial/docker-compose.yml
version: "3"
services:
writer:
image: dodo_tutorial
build:
dockerfile: ./Dockerfile
context: .
volumes:
- ./writer:/app
- ./time.log:/time.log
working_dir: /app
command: make runserver
reader:
depends_on: [writer]
image: dodo_tutorial
volumes:
- ./reader:/app
- ./time.log:/time.log
working_dir: /app
command: make runserver
cat /tmp/tutorial/Dockerfile
FROM python:3.7-alpine
RUN apk add make
Let’s test if it works:
cd /tmp/tutorial
docker-compose up
Creating network "tutorial_default" with the default driver
Creating tutorial_writer_1 ... done
Creating tutorial_reader_1 ... done
Attaching to tutorial_writer_1, tutorial_reader_1
reader_1 | echo "Starting reader service"
reader_1 | Starting reader service
writer_1 | echo "Starting writer service"
reader_1 | tail -f ../time.log
writer_1 | Starting writer service
reader_1 | 1586270720.460995
Using the docker-compose command¶
We’d like to be able to bring this docker system up from any directory with the
dodo docker-compose up
command. To facilitate this, we’ll create
a new configuration layer in /tmp/tutorial/.dodo_commands/docker.yaml
.
# Create a new file /tmp/tutorial/.dodo_commands/docker.yaml
DOCKER_COMPOSE:
cwd: ${/ROOT/project_dir}
To enable the layer we add it to the LAYERS
of the main configuration file.
Note that this layer is always loaded.
# In: /tmp/tutorial/.dodo_commands/config.yaml
LAYERS:
- docker.yaml
We can now run dodo docker-compose up
to bring the stack up. Remember that
you can use the --confirm
flag to see the command before it’s executed. You can also use
the --echo
flag for this purpose.
The docker-compose
command comes standard with
Dodo Commands. If you want to see its location and inspect its contents, you can use the
dodo which
command:
dodo which docker-compose
/some/path/to/dodo_docker_commands/docker-compose.py
Tip
We could also have added the DOCKER_COMPOSE
section directly to config.yaml
. It’s
up to you to decide when parts of the configuration should be moved to a separate layer file.
Detail sections¶
Open the adjacent tabs for more detail sections
We can add an alias for docker-compose up
so we don’t have to type too much. With this
alias we can start the Docker system with dodo up
:
# /tmp/tutorial/.dodo_commands/config.yaml
ROOT:
# other stuff
aliases:
up: docker-compose up
Aliases that should be available in any environment can be added to the global configuration
file. To find out where this file lives run dodo which --global-config
. Let’s add an alias
there for docker-compose up --detach
:
# ~/.dodo_commands/config
[alias]
upd = docker-compose up --detach
When we try out the command with dodo upd
it will start both containers.
Dodo Commands comes with various useful commands to work with Docker containers. For example,
dodo docker-kill
will show you a menu in which you can select the container that you want to kill:
dodo docker-kill
1 - tutorial_writer_1
2 - tutorial_reader_1
Select a container:
The dodo docker-exec
command lets you execute a command in a selected docker container.
dodo docker-exec --cmd ls
0 - exit
1 - tutorial_reader_1
2 - tutorial_writer_1
Select a container:
2
Makefile write_periodically.py
Running a command inside a container¶
We’ll now take a look at how we can add a command script that runs inside a Docker container.
We’ll first add a new command (mk-greet
) without worrying about Docker. This new command
script will run the greeting
Makefile command.
# In: /tmp/tutorial/writer/Makefile
greeting:
echo "Hello ${GREETING}"
We’ll add a mk-greet.py
script to /tmp/tutorial/commands
that sets the GREETING
environment variable and then runs make greeting
:
# In: /tmp/tutorial/commands/mk-greet.py
from dodo_commands import Dodo
Dodo.parser.add_argument("greeting")
Dodo.run(
["make", "greeting", "GREETING=%s" % Dodo.args.greeting],
cwd=Dodo.get("/MAKE/cwd")
)
Remember that we have to run this as dodo writer.mk-greet
so that the server.writer.yaml
layer
is loaded. Let’s see what it currently looks like:
dodo writer.mk-greet hi --confirm
(/tmp/tutorial/writer) make greeting GREETING=hi
confirm? [Y/n]
We have a new command script but it is not yet running inside the tutorial_writer_1
Docker container.
Let’s fix this.
We first need to tell Dodo Commands that the mk-greet
command is dockerized:
# /tmp/tutorial/.dodo_commands/server.writer.yaml
ROOT:
# other stuff
decorators:
docker: [mk-greet]
We also need to specify in which container the mk-greet
command should run:
# /tmp/tutorial/.dodo_commands/writer.yaml
DOCKER_OPTIONS:
mk-greet:
container: tutorial_writer_1
Tip
The keys in the DOCKER_OPTIONS
take wild-cards, so instead of mk-greet
we could have used
*
. In our example, this would mean that any dockerized script uses the
tutorial_writer_1
container.
Next, we need to update the value of ${/MAKE/cwd}}
because it should point to a location
in the container (and not in the host machine):
# In: /tmp/tutorial/.dodo_commands/writer.yaml
MAKE:
cwd: /app
When we try again we see that the command is prefixed with the proper Docker arguments:
dodo writer.mk-greet hi --confirm
(/tmp/tutorial) docker exec \
--interactive --tty \
--workdir=/app \
tutorial_writer_1 \
make greeting GREETING=hi
confirm? [Y/n]
echo "Hello hi"
Hello hi