What does draky solve?
draky is a wrapper helping interact with docker-compose. It dynamically creates a docker-compose.yml
and .env
files based on provided configurations that are made to be easier to manage and reuse than what
docker compose offers out of the box.
It addresses several common annoyances of Docker Compose.
Encapsulation
Volumes
Problem
You want to make your service definition easily reusable. For this purpose, you moved it
from docker-compose.yml
to a different directory to keep it there alongside all its dependencies,
including volumes. Then you referenced it in docker-compose.yml
with the extends
property.
However, there is a problem: volumes of extended services must be relative to the docker-compose.yml
file,
not your service file. This means your service definition cannot be completely decoupled from
the docker-compose.yml
file. It's always coupled to its location relative to the docker-compose.yml
file.
As a result, you cannot freely move it around; if you copy it to another directory to adapt it for another environment, it won't work without also adjusting the volume paths.
Wouldn't it be nice if you could define volumes relative to the service file? If that were the case, you wouldn't need to care where your service file is located. You could treat it as a truly decoupled and encapsulated configuration.
Solution
draky dynamically creates a docker-compose.yml
based on a recipe and, in the process,
translates volume paths from those relative to the service file into ones relative to the
docker-compose.yml
file.
This means you can use volume paths relative to the service file, and the final docker-compose.yml
will still find everything it needs because paths will be adjusted. This allows creating truly
decoupled configurations.
Variables
Problem
Traditionally, if you want to have a set of variables available in the service definition, you either
define them in an .env
file or pass them directly to the docker-compose
command. Both options
have major drawbacks. Passing all variables every time you run docker-compose
is inconvenient, and
using a single .env
file is not much better when you have multiple services — you may prefer to keep
service-specific variables alongside the service definition rather than in a single catch‑all file.
Solution
draky compiles an .env
file from multiple *.dk.yml
files that you can place anywhere in your
.draky
directory. This enables a cleaner organization of variables — you can keep service‑specific
variables alongside the service definition, further improving encapsulation.
This is the configuration structure you could have:
.draky/
env/
dev/
.env <-- This is automatically created including all variables from all *.dk.yml files.
docker-compose.recipe.yml <-- the recipe is almost the same in spec as `docker-compose.yml`, but because of this indirection, we can fix some annoyances of `docker-compose.yml` .
docker-compose.yml <-- This is automatically created from the above recipe. The created `docker-compose.yml` doesn't need `draky` to run.
services/
mariadb/ <-- This directory encapsulates all `mariadb` configuration.
commands/ <-- This directory contains all commands specific to the `mariadb` definition.
mariadb.database.dk.sh <-- This is a command-script that will run inside the `database` service. Can be invoked as `draky mariadb` anywhere in the project. This command assumes that the `mariadb` definition will be imported to the docker-compose.recipe.yml as a `database` service.
resources/ <-- This directory is the only volume used by the `mariadb`.
init.d/ <-- This directory contains initialization scripts that will be executed inside the container on startup.
0-setup.sh
override/
etc/
mysql/
my.cnf <-- This file is copied into the container's filesystem and overrides the default `my.cnf` file.
variables.dk.yml <-- This contains variables specific to the mariadb service.
services.yml <-- This contains definition of the mariadb service.
.gitignore <-- Excludes `local.dk.yml` file from being committed to the repository.
core.dk.yml <-- Includes project id.
local.dk.yml <-- This file contains variables that are not supposed to be committed to the repository.
template.dk.yml <-- Metadata information about this configuration structure.
As you can see, mariadb
definition is completely encapsulated with its all dependencies in a single directory.
It can be easily copy-pasted to a different project.
Commands
Problem
You have many projects and need access to commands inside containers. It might be the
composer
command or the console
command in the case of a Symfony app. Or maybe you want a
command that dumps a database, runs migrations, and so on.
Traditionally, you would write a script that runs docker-compose exec [service]
and executes
the command inside the container, passing arguments along the way. This works, but it’s also
awkward for several reasons:
- Your script includes not only the logic you want to run inside the container, but also
boilerplate for executing scripts inside containers (
docker-compose exec [service]
). - For longer scripts, you might have to use a heredoc to pass the script into the container, which can be cumbersome and hard to read.
- Your script runs only from a single location. To run it from elsewhere, you need to
cd
to the script’s directory or use a path. Depending on your current working directory, you may need different commands. Adding the script’s location to your$PATH
comes with a different set of problems. - It’s not easy to pass all your project’s environment variables into the container, so they’re available to your command.
Solution
draky lets you define commands and auto‑wires them in a way that minimizes boilerplate and adds several useful features.
- To create a draky command, create a
[command name].[service].dk.sh
file anywhere in the.draky/
directory. The file contains only the logic that should run inside the container. This is more IDE‑friendly (heredocs may not be highlighted correctly). - You can execute such a command anywhere in the project’s directory tree with a simple
draky [command name]
. - Any additional arguments (and stdin) are passed to the command automatically.
- All environment variables defined in draky’s configuration are available inside the command’s script.
- Commands can also be scoped to specific environments (draky can have different configurations, including commands, for multiple environments).
Modifying containers at runtime
Problem
Traditionally, if you want to modify “vanilla” containers (like php
or nginx
) to apply your custom
configuration, you need either:
- To create a custom image based on a “vanilla” one, with some modified files. This requires building/hosting images and is not very convenient for development or prototyping.
- To modify a “vanilla” image by mounting a volume with a modified file. This is fine as long as there are only a few files to modify. The problem is that each overridden file requires its own volume, which slowly turns your service definition into a mess.
Wouldn't it be nice if you could override any file in the container's filesystem at any time—without having to remember to keep a volume for it?
Solution
draky-entrypoint
lets you override as many files as you want by creating a directory
tree that is copied into the container’s filesystem, replacing any files that already exist. This happens
on container startup, before its main process is executed, so configuration changes from the
override are applied to the main process. This lets you manage file overrides without
modifying the service definition, making them much easier to maintain.
What’s more, draky-entrypoint
lets you pass variables to overridden files via its templating system.
It can make overrides dynamic, based on variables.
You won’t need to think which file to modify each time you want to change something you often
change — just expose the right variable and manage them from a single file.
draky-entrypoint
also provides a simple way to create a host user inside any container. Because this
is done dynamically at container startup, it doesn’t matter what ID the host user has—it will be
checked and applied automatically, which improves the reusability of configurations across different hosts.
draky-entrypoint
also provides a generic way to run initialization scripts inside any container,
even those that do not provide this functionality out of the box, enabling further dynamic customization.