This library is intended to make it easier to develop applications when using docker. It allows you to set up initial docker configuration, and to execute commands inside the main docker container from the host machine. It aims to be similar to Fabric, but for local development with docker applications.
A typical setup looks something like this:
-
A
docker-compose.yml
(or a docker setup that has been created manually) with at least one "main" python container. For instance, if you are developing a web application, you might have aredis
container, adatabase
container, and aweb
container, and theweb
container is the one that contains your python code. You can also use custom@dm.command
functions to set up your docker cluster without docker-compose, if you want. -
A script that is the entrypoint for your management command. I typically use
./do
in the project's root directory. It looks something like this (for a Django application, commands will be different depending on the language/framework you are using and your goals):#!/usr/bin/env python import os import dockerman as dm # enable buildkit os.environ["DOCKER_BUILDKIT"] = "1" os.environ["COMPOSE_DOCKER_CLI_BUILD"] = "1" @dm.command(passthrough=True) def bash(args): """Bash shell on the web container.""" dm.crun("bash", args) @dm.command() def start(args): """Start all services.""" dm.run("docker-compose up -d") @dm.command() def stop(args): """Stop all services.""" dm.run("docker-compose stop") @dm.command() def dbshell(args): """Execute a database shell.""" dm.crun("psql -U myuser mydatabase", container="database") @dm.command() def shell(args): """Open a django shell on the web container.""" dm.crun("django-admin shell", args) @dm.command(passthrough=True) def manage(args): """Run a django management command.""" dm.crun("django-admin", args) @dm.command( dm.option('--name', help='Container name'), ) def reset_container(args): """Reset a container.""" dm.run(f"docker-compose stop {args.name}") dm.run(f"docker-compose rm {args.name}") dm.run("docker-compose up -d") if __name__ == "__main__": dm.set_default_container("web") dm.main()
With this setup, you can run commands like ./do help
, ./do shell
, etc.
This is a decorator that turns a function into a command. The command will have
the same name as the function it decorates, and the docstring will be the
documentation that appears when you type ./do help
or ./do help [command]
.
All underscores will be converted to hyphens in the resulting command name.
If you specify passthrough=True
, all extra command line arguments will be
passed to any dm.crun
or dm.run
statements executed within the function
(this is the purpose of the command function receiving the args
parameter,
and passing that same args
parameter to dm.crun
and dm.run
).
For example, if you'd like to run Django management commands in the web container:
@dm.command(passthrough=True)
def manage(args):
"""Run Django management commands."""
dm.crun("django-admin", args)
Then when you run something like:
$ ./do manage makemigrations --name add_user_is_active_field accounts
The makemigrations --name add_user_is_active_field accounts
will be passed
through to django-admin
on the container.
Another possible argument to @dm.command
is default=True
. This will cause
the system to use this command for everything that doesn't match any other
command. For instance, instead of defining a shell
command for django-admin,
you could do the following:
@dm.command(passthrough=True, default=True)
def manage(args):
"""Run Django management commands."""
dm.crun("django-admin", args)
Then, if you type ./do shell
and there is no matching shell
command
defined, it will act as though you typed ./do manage shell
.
This runs a command on the host. Things like
dm.run('docker network add test')
are typical.
This runs a command in a docker container. Passing container="web"
will tell
it to run on the "web" container. If you do not pass container
, it will use
the default container specified in dm.set_default_container
.
You can pass one or more dm.option
arguments to the dm.command
decorator.
These will set up argument options for your command, using the argparse
module. They are passed directly to parser.add_argument
, so they have the
same parameters.
An example:
@dm.command(@dm.option("--name", dest="name", help="Your name"))
def hello(args):
print(f"Hello, {args.name}!")
These are logging statements. Each one has it's own color indicative of the type of message you want to show. For example:
@dm.command(@dm.option("--name"))
def hello(args):
if args.name.lower() in ("tyler", "steve", "james"):
dm.error(f"Sorry, your name cannot be {args.name}. Get a new one.")
sys.exit(1)
print(f"Hello, {args.name}!")