Skip to main content

Basics

Introduction

draky is a Docker-based environment manager made to help you manage your Docker environments whenever you need them. Unlike other tools like Lando, Docksal, or DDEV, this tool was designed to be unopinionated and keep you as close to your docker-compose.yml as possible. This tutorial will show you how to work with it.

Installation

First, install draky.

Initializing a new project

To initialize a new project, run draky env init in the new project's root directory, and choose the default template.

$ draky env init
draky core has been turned off.
draky core is live.
Leaving the context: 'None'.
draky core has been turned off.
Entering the context: '/home/luken/IdeaProjects/test-draky/.draky'.
draky core is live.
Enter project id: test-draky

[0]: default

Enter template number: 0
Project has been initialized.

This is the directory structure that has been created:

.draky/
commands/
README.txt
env/
dev/
docker-compose.recipe.yml
services/
README.md
.gitignore
core.dk.yml
local.dk.yml.example
template.dk.yml

Recipe, docker-compose.yml, and services

The default template comes with an example docker-compose.recipe.yml file for the default dev environment. It looks just like a regular docker-compose.yml file, but it's not. draky processes the recipe to generate the final docker-compose.yml file. Working with a recipe gives us some advantages.

What are these advantages?

Let's create a service definition in a separate directory and reference it from our recipe.

.draky/
commands/
README.txt
env/
dev/
docker-compose.recipe.yml
services/
mariadb/ <-- this is new
services.yml <-- this is new
README.md
.gitignore
core.dk.yml
local.dk.yml.example
template.dk.yml
# .draky/env/dev/docker-compose.recipe.yml
services:
database:
extends:
file: ../../services/mariadb/services.yml
service: mariadb
# .draky/services/mariadb/services.yml
services:
mariadb:
image: mariadb:12
environment:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_DATABASE: main
ports:
- "3306:3306"

Assuming our docker-compose.recipe.yml file would be a normal docker-compose.yml file, the paths to volumes in the services.yml file would need to be relative to the parent docker-compose.yml file.

Part of the processing rewrites paths so that they can be specified relative to the services.yml file, allowing us to encapsulate our services and their dependencies and make them easier to reuse.

Another thing this processing enables is addons, which are powerful scripts that apply custom logic to the generation of the docker-compose.yml file, making your life easier.

But more on them later.

Let's build the actual docker-compose.yml file from our recipe, by running draky env build.

This is what it should look like:

# .draky/env/dev/docker-compose.yml

# This file is autogenerated because the environment contains a recipe. To modify the services, modify the recipe.
services:
database:
environment:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_DATABASE: main
image: mariadb:12
ports:
- 3306:3306

This is the heart of your environment. That's what will be used, when you run draky env up.

You don't need to run draky env build each time you change your recipe, though. The docker-compose.yml file is automatically regenerated each time you start environment.

Variables

draky allows us to define variables that will be available in the docker-compose.yml file. They can be stored in configuration files with the dk.yml extension, allowing us to better organize our configuration.

Let's expose some of MariaDB's service properties as variables.

# .draky/services/mariadb/variables.dk.yml
variables:
MARIADB_VERSION: 12
MARIADB_DATABASE: main
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_EXPOSED_PORT: 3306
# .draky/services/mariadb/services.yml
services:
mariadb:
image: "mariadb:${MARIADB_VERSION}"
environment:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: "${MARIADB_ALLOW_EMPTY_ROOT_PASSWORD}"
MARIADB_DATABASE: "${MARIADB_DATABASE}"
ports:
- "${MARIADB_EXPOSED_PORT}:3306"

And that's it! Now we can edit all important bits in a single, clean file.

There is more to variables. You can construct variable values from other variables. Let's see what it looks like:

# .draky/services/mariadb/variables.dk.yml
variables:
MARIADB_VERSION: 12
MARIADB_IMAGE: "mariadb:${MARIADB_VERSION}" # <-- added dynamic variable
MARIADB_DATABASE: main
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true
MARIADB_EXPOSED_PORT: 3306
# .draky/services/mariadb/services.yml
services:
mariadb:
image: "${MARIADB_IMAGE}" # <-- dynamic variable
environment:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: "${MARIADB_ALLOW_EMPTY_ROOT_PASSWORD}"
MARIADB_DATABASE: "${MARIADB_DATABASE}"
ports:
- "${MARIADB_EXPOSED_PORT}:3306"

You can also define dependencies between variable files, ensuring the correct order of variable loading, which would be important when defining dynamic variables depending on variables from other files. You can learn more about that in the documentation.

The point is that each configuration has parts that it would be useful to be able to change easily, and draky's variables allow us to do that.

Now let's build the docker-compose.yml file again with draky env build.

We should see something like this:

# .draky/env/dev/docker-compose.yml

# This file is autogenerated because the environment contains a recipe. To modify the services, modify the recipe.
services:
database:
environment:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: ${MARIADB_ALLOW_EMPTY_ROOT_PASSWORD}
MARIADB_DATABASE: ${MARIADB_DATABASE}
image: ${MARIADB_IMAGE}
ports:
- ${MARIADB_EXPOSED_PORT}:3306

Notice that values are now replaced with variables. All variables are copied to the .draky/env/dev/.env file, where you can look up, what variable values are actually used in the docker-compose.yml file.

However, there is a simpler way, to look up, what the actual values in the docker-compose.yml file are.

Run draky env build -s to build the docker-compose.yml file with variable substitution.

That way you don't need to look at the .env file to see, what values are actually used.

Commands

Host commands

The next big feature draky provides is custom commands. You can define commands as normal scripts with the dk.sh extension.

Let's do that.

# .draky/test-command.dk.sh

#!/usr/bin/env sh
ls -la /.dockerenv

Our command is now available as draky test-command. However, when you try to run it, you will see:

$ draky test-command
ls: cannot access '/.dockerenv': No such file or directory

This is because this command runs on the host, so the /.dockerenv file is not available.

Host-run commands can be useful if we need to process output coming from multiple containers.

Container commands

We can also create commands that run inside containers!

Let's create a command that will pass arguments to the mariadb client inside the mariadb container.

# .draky/services/mariadb/commands/mariadb.database.dk.sh

#!/usr/bin/env sh
mariadb -u root "$@"

But for this to work, we need to start our environment, so let's run draky env up first.

Now when we run draky mariadb --version we will see something like:

$ draky mariadb --version
mariadb from 12.0.2-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using EditLine wrapper

We managed to use the mariadb client from the container, as if it were running directly on the host!

This worked because of the naming scheme of the command file. Basically the database part of the mariadb.database.dk.sh command filename tells draky that this script should be executed inside the database container. And database is the name of the service defined in the docker-compose.recipe.yml file.

But there is more!

We can also run commands inside the container as a specific user. To do this, we need to add a special "companion" file to the command. That file contains some additional information about the command.

# .draky/services/mariadb/commands/mariadb.database.dk.sh.yml

user: 1000 # Either id or username can be used here.

Note: When wrapping a single command inside a container, like in our mariadb example, it's good practice to redirect stdin into it. That way you can pipe data from the host if needed, and so on.

So, it will look like this:

# .draky/services/mariadb/commands/mariadb.database.dk.sh

#!/usr/bin/env sh
mariadb -u root "$@" < /dev/stdin

Variables in commands

To make your commands configurable, you can use variables. Yes, variables that were explained earlier are available inside the commands, even if these commands run inside a container!

So if we change our command like that:

# .draky/services/mariadb/commands/mariadb.database.dk.sh

#!/usr/bin/env sh
mariadb -u root "$@" < /dev/stdin
echo "MARIADB_IMAGE: ${MARIADB_IMAGE}"

You would get output:

$ draky mariadb --version
mariadb from 12.0.2-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using EditLine wrapper
MARIADB_IMAGE: mariadb:12

More about custom commands can be found in the documentation.

Multiple environments

draky also allows you to create multiple environments, each with its own docker-compose.recipe.yml file, its own variables, and commands. All configuration files and commands can be scoped to specified environments.

You can learn more about this feature in the documentation.

Addons

Another concept worth briefly mentioning is addons. Addons let you process a recipe before it’s converted into a docker-compose.yml file. For example, they can automatically attach a custom entrypoint to any service to provide additional functionality.

We provide the addon, which does exactly that: draky-entrypoint.

Among others, it provides generic ways for:

  • Running initialization scripts in containers.
  • Overriding files inside the containers.
  • Creating the host user inside containers.

Its powerful features deserve a dedicated tutorial, that can be found here.