{"id":13585454,"url":"https://github.com/rycus86/docker-pygen","last_synced_at":"2025-07-15T11:31:34.779Z","repository":{"id":64476330,"uuid":"96158665","full_name":"rycus86/docker-pygen","owner":"rycus86","description":"Automatic configuration generation based on Docker events and state.","archived":false,"fork":false,"pushed_at":"2024-06-19T01:03:26.000Z","size":187,"stargazers_count":8,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-09T03:24:10.538Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rycus86.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-07-03T23:53:46.000Z","updated_at":"2022-12-24T12:59:58.000Z","dependencies_parsed_at":"2024-11-06T03:32:45.536Z","dependency_job_id":null,"html_url":"https://github.com/rycus86/docker-pygen","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rycus86/docker-pygen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdocker-pygen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdocker-pygen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdocker-pygen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdocker-pygen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rycus86","download_url":"https://codeload.github.com/rycus86/docker-pygen/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdocker-pygen/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265431549,"owners_count":23764031,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T15:04:57.027Z","updated_at":"2025-07-15T11:31:34.467Z","avatar_url":"https://github.com/rycus86.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# docker-pygen\n\nConfiguration generator based on Docker containers state and parameters.\n\n[![Travis](https://travis-ci.org/rycus86/docker-pygen.svg)](https://travis-ci.org/rycus86/docker-pygen)\n[![Build Status](https://img.shields.io/docker/build/rycus86/docker-pygen.svg)](https://hub.docker.com/r/rycus86/docker-pygen)\n[![Coverage Status](https://coveralls.io/repos/github/rycus86/docker-pygen/badge.svg?branch=master)](https://coveralls.io/github/rycus86/docker-pygen?branch=master)\n[![Code Climate](https://codeclimate.com/github/rycus86/docker-pygen/badges/gpa.svg)](https://codeclimate.com/github/rycus86/docker-pygen)\n[![Docker Image Layers](https://images.microbadger.com/badges/image/rycus86/arm-nginx.svg)](https://microbadger.com/images/rycus86/docker-pygen \"Get your own image badge on microbadger.com\")\n\n## Motivation\n\nAs we break our applications down to individual microservices more and more\nthe harder it gets to configure the supporting infrastructure around them.\nIf we think about managing HTTP proxying to them with servers like *Nginx*\nor configuring any other system that has to know about a set of (or all)\nof the running services - that can become quite an overhead done manually.\n\nIf you're using Docker to run those microservices then this project could\nprovide an easy solution to the problem.\nBy inspecting the currently running containers and their settings it can\ngenerate configuration files for basically anything that works with those.\nIt can also notify other services about the configuration change by\nsignalling or restarting them.\n\n## Usage\n\nTo run it as a Python application (tested on versions 2.7, 3.4 and 3.6)\nclone the project and install the dependencies:\n\n```shell\npip install -r requirements.txt\n```\n\nThen run it as `python cli.py \u003cargs\u003e` where the arguments are:\n\n```text\nusage: cli.py [-h] --template TEMPLATE [--target TARGET]\n              [--restart \u003cCONTAINER\u003e] [--signal \u003cCONTAINER\u003e \u003cSIGNAL\u003e]\n              [--interval \u003cMIN\u003e [\u003cMAX\u003e ...]] [--events \u003cEVENT\u003e [\u003cEVENT\u003e ...]]\n              [--swarm-manager] [--workers \u003cTARGET\u003e [\u003cTARGET\u003e ...]]\n              [--retries RETRIES] [--no-ssl-check] [--one-shot]\n              [--docker-address \u003cADDRESS\u003e] [--debug]\n\nTemplate generator based on Docker runtime information\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --template TEMPLATE   The base Jinja2 template file or inline template as\n                        string if it starts with \"#\"\n  --target TARGET       The target to save the generated file (/dev/stdout by\n                        default)\n  --restart \u003cCONTAINER\u003e\n                        Restart the target container, can be: ID, short ID,\n                        name, Compose service name, label [\"pygen.target\"] or\n                        environment variable [\"PYGEN_TARGET\"]\n  --signal \u003cCONTAINER\u003e \u003cSIGNAL\u003e\n                        Signal the target container, in \u003ccontainer\u003e \u003csignal\u003e\n                        format. The \u003ccontainer\u003e argument can be one of the\n                        attributes described in --restart\n  --interval \u003cMIN\u003e [\u003cMAX\u003e ...]\n                        Minimum and maximum intervals for sending\n                        notifications. If there is only one argument it will\n                        be used for both MIN and MAX. The defaults are: 0.5\n                        and 2 seconds.\n  --repeat \u003cSECONDS\u003e    Optional interval in seconds to re-run the target\n                        generation after an event and execute the action if\n                        the target has changed. Defaults to 0 meaning the\n                        generation will not be repeated.\n  --events \u003cEVENT\u003e [\u003cEVENT\u003e ...]\n                        Docker events to watch and trigger updates for\n                        (default: start, stop, die, health_status)\n  --swarm-manager       Enable the Swarm manager HTTP endpoint on port 9411\n  --workers \u003cTARGET\u003e [\u003cTARGET\u003e ...]\n                        The target hostname of PyGen workers listening on port\n                        9412 (use \"tasks.service_name\" for Swarm workers)\n  --retries RETRIES     Number of retries for sending an action to a Swarm\n                        worker\n  --no-ssl-check        Disable SSL verification when loading templates over\n                        HTTPS (not secure)\n  --one-shot            Run the update once and exit, also execute actions if\n                        the target changes\n  --docker-address \u003cADDRESS\u003e\n                        Alternative address (URL) for the Docker daemon\n                        connection\n  --metrics \u003cPORT\u003e      HTTP port number for exposing Prometheus metrics\n                        (default: 9413)\n  --debug               Enable debug log messages\n```\n\nThe application will need access to the Docker daemon too.\n\nYou can also run it as a Docker container to make things easier:\n\n```shell\ndocker run -d --name config-generator                         \\\n              -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n              -v shared-volume:/etc/share/config              \\\n              -v $PWD/template.conf:/etc/share/template.conf  \\\n              --template /etc/share/template.conf             \\\n              --target   /etc/share/config/auto.conf          \\\n              --restart  config-loader                        \\\n              --signal   web-server HUP                       \\\n              rycus86/docker-pygen\n```\n\nThis command will:\n\n- attach the Docker socket from `/var/run/docker.sock`\n- attach a shared folder from the `shared-volume` to `/etc/share/config`\n- attach the template file `template.conf` from the current host directory \n  to `/etc/share/template.conf`\n- use the template (at `/etc/share/template.conf` inside the container)\n- write to the `auto.conf` target file on the shared volume\n  (at `/etc/share/config/auto.conf` inside the container)\n- restart containers matching \"config-loader\" when the configuration file is updated\n- send a *SIGHUP* signal to containers matching \"web-server\"\n\nMatching containers can be based on container ID, short ID, name, Compose or Swarm service name.\nYou can also add it as the value of the `pygen.target` label or as the value of the \n`PYGEN_TARGET` environment variable.\n\nThe connection to the Docker daeamon can be overridden from the default\nlocation to an alternative (for TCP for example) using the `--docker-address` flag.\nFor testing (or for other reasons) the app can also run in `--one-shot` mode\nthat generates the configuration using the template once and exits without\nwatching for events (this also executes any actions given if the target file changes).\n\nThe Docker image is available in three flavors:\n\n- `amd64`: for *x86* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/docker-pygen:amd64.svg)](https://microbadger.com/images/rycus86/docker-pygen:amd64 \"Get your own image badge on microbadger.com\")\n- `armhf`: for *32-bits ARM* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/docker-pygen:armhf.svg)](https://microbadger.com/images/rycus86/docker-pygen:armhf \"Get your own image badge on microbadger.com\")\n- `aarch64`: for *64-bits ARM* hosts  \n  [![Layers](https://images.microbadger.com/badges/image/rycus86/docker-pygen:aarch64.svg)](https://microbadger.com/images/rycus86/docker-pygen:aarch64 \"Get your own image badge on microbadger.com\")\n\nAll of these are built on and uploaded from [Travis](https://travis-ci.org/rycus86/docker-pygen)\nwhile `latest` is a multi-arch manifest on [Docker Hub](https://hub.docker.com/r/rycus86/docker-pygen)\nso using that would select the appropriate image based on the host's processor architecture.\n\nThe application exposes [Prometheus](https://prometheus.io/) metrics\nabout the number of calls and the execution times of certain actions.\n\n## Templating\n\nTo generate the configuration files, the app uses [Jinja2 templates](http://jinja.pocoo.org/docs).\nTemplates have access to these variables:\n\n- `containers` list containing a list of *running* Docker\n  containers wrapped as `models.ContainerInfo` objects on a `resources.ContainerList`\n- `services` list containing Swarm services with their running tasks (desired state)\n  using `models.ServiceInfo` and `models.TaskInfo` objects wrapped in\n  `resources.ServiceList` and `resources.TaskList` collections.\n- `all_containers` *lazy-loaded* list of *all* Docker containers (even if not running)\n- `all_services` *lazy-loaded* list of Swarm services with *all* their tasks\n  (even if not in running desired state)\n- `nodes` *lazy-loaded* list of Swarm nodes as `models.NodeInfo` objects wrapped\n  in a `resources.ResourceList` list\n- `own_container_id` that contains the ID of the container the app is running in\n  or otherwise `None`\n- `read_config` that helps reading configuration parameters from key-value files\n  or environment variables and also full configuration files (certificates for example),\n  see [docker_helper](https://github.com/rycus86/docker_helper) for more information and usage\n\nTemplates can be loaded from a file, from an HTTP/HTTPS address or can be given inline if\nthe `--template` parameters starts with a `#` sign.\n\nA small example from a template could look like this:\n\n```\n{% set server_name = 'test.example.com' %}\nupstream {{ server_name }} {\n    {% for container in containers\n          if  container.networks.first_value.ip_address\n          and container.ports.tcp.first_value %}\n        # {{ container.name }}\n        server {{ container.networks.first_value.ip_address }}:{{ container.ports.tcp.first_value }};\n    {% endfor %}\n}\n```\n\nThis example from the [nginx.example](https://github.com/rycus86/docker-pygen/blob/master/tests/templates/nginx.example)\nfile would output `server_name` as the value set on the first line then iterate\nthrough the containers having an IP address and TCP port exposed to finally output\nthem prefixed with the container's name.\n\nThe available properties on a `models.ContainerInfo` object are:\n\n- `raw`: The original container object from [docker-py](https://github.com/docker/docker-py)\n- `id`: The container's ID\n- `short_id`: The container's short ID\n- `name`: The container's name\n- `image`: The name of the image the container uses\n- `status`: The current status of the container\n- `health`: The health status of the container or `unknown` if it does not have health checking\n- `labels`: The labels of the container (as `EnhancedDict` - see below)\n- `env`: The environment variables of the container as `EnhancedDict`\n- `networks`: The list of networks the container is attached to (as `NetworkList`)\n- `ports`: The list of ports exposed by the container as `EnhancedDict` having\n  `tcp` and `udp` ports as `EnhancedList`\n\nThe `utils.EnhancedDict` class is a Python dictionary extension to allow referring to\nkeys in it as properties - for example: `container.ports.tcp` instead of\n`container['ports']['tcp']`. Property names are also case-insensitive.  \nThe `models.ContainerInfo` class extends `utils.EnhancedDict` to provide these features.\n\nThe `utils.EnhancedList` class is a Python list extension having additional properties\nfor getting the `first` or `last` element and the `first_value` - e.g. the first element\nthat is not `None` or empty.\n\nThe `resources.ResourceList` extends `EnhancedList` to provide a `matching(target)` method\nthat allows getting the first element of the list having a matching ID or name.\nFor convenience, a `not_matching` method is also available.\n\nThe `resources.ContainerList` extends the `matching` method to also match by Compose\nor Swarm service name for containers.\nIt also supports the `healthy` property that filters the list for containers with healthy\nstate while the `with_health` method can be used to filter for a given health state.\nThe `self` property returns the `models.ContainerInfo` instance for the running\napplication itself, if appropriate.\n\nSwarm services use the `models.ServiceInfo` class with these properties:\n\n- `raw`: The original service object from the API\n- `id`: The ID of the service\n- `short_id`: The short ID of the service\n- `name`: The name of the service\n- `version`: The current Swarm version of the service\n- `image`: The image used by the service\n- `labels`: The labels attached to the service (not the tasks)\n- `ports`: Contains two lists for `tcp` and `udp` ports for the published ports'\n  targets used internally by the containers\n- `networks`: The networks used by the service (except `ingress`)\n- `ingress`: The Swarm ingress network's details\n- `tasks`: The current Swarm tasks that belong to the service\n\nTasks use the `models.TaskInfo` class and have these properties available:\n\n- `raw`: The original task attributes (`dict`-like) from the API\n- `id`: The ID of the task\n- `name`: The name of the task generated as `\u003cservice_name\u003e.\u003cslot\u003e.\u003ctask_id\u003e` for\n  replicated services or `\u003cservice_name\u003e.\u003cnode_id\u003e.\u003ctask_id\u003e` for global services.\n- `node_id`: The ID of the Swarm node the task is scheduled on\n- `service_id`: The ID of the service the task belongs to\n- `slot`: The slot number for tasks in replicated services\n- `container_id`: The ID of the container the task created\n- `image`: The image the container of the task uses\n- `status`: The status of the task\n- `desired_state`: The desired state of the task\n- `labels`: Labels assigned to the task and its containers, also including:\n  - `com.docker.swarm.service.id`: The ID of the service the task belongs to\n  - `com.docker.swarm.service.name`: The name of the service the task belongs to\n  - `com.docker.swarm.task.id`: The ID of the task\n  - `com.docker.swarm.task.name`: The name of the task\n  - `com.docker.swarm.node.id`: The ID of the Swarm node the task is scheduled on\n- `env`: Environment variables used on the container created by the task\n- `networks`: The list of networks attached to the task\n\nThe `resources.ServiceList` extends `matching` by Swarm service name and the\n`resources.TaskList` can also match by container ID, service ID or service name.\nTasks can also be filtered using their status and the `with_status` method.\nBoth of them support the `self` property, that returns the `models.ServiceInfo`\nor the `models.TaskInfo` instance respectively,\nwhere the current application is running, if appropriate.\n\nThe `resources.NetworkList` class adds matching by network ID\nor network instance with an `id` property.\nIt also accept other objects with networking settings\n(one that has a `networks` property, like `ContainerInfo`) and\nmatches the networks against its network list.\nYou can also pass another `resources.NetworkList` to it to give you\nthe common networks that are present on both lists.\n\nThe networks for __containers__ have the `id`, `name` and a single `ip_address` properties.\nFor __services__ the networks have a list of `ip_addresses` plus a `gateway` property.\n__Task__ networks also include the network `labels` and an `is_ingress` flag as well.\nFinally the __ingress__ network on services has a `port` property with lists of `tcp` and\n`udp` ports published on the Swarm ingress.\n\nAn example for matching could be containers on the same network in a Compose project:\n```\n{% set reference = containers.matching('web').first %}\ntargets:\n{% for container in containers %}\n  - \"http://{{ container.networks.matching(reference).first.ip_address }}:{{ container.ports.tcp.first_value }}/{{ container.name }}\"\n{% endfor %}\n```\n\nThis would take the `web` container as a reference and list targets with\nthe IP address taken from the first matching network using the reference.\nA Swarm example would be:\n\n```\n{% set own_service = services.self %}\n\nCommon networks:\n{% for service in services %}\n  {% for task in service.tasks %}\n    {% if task.networks.not_matching('ingress').matching(own_service.networks).first_value %}\n    - {{ task.name }} in {{ service.name }}\n    {% endif %}\n  {% endfor %}\n{% endfor %}\n```\n\nThe snippet above would print the name of the tasks (and the name of their services)\nwhich share the same networks as the current *PyGen* app running in a container,\nexcept for the network called `ingress`.\nNote, that `task.networks.not_matching('ingress').matching(own_service)`\nwould also work for matching, but it is perhaps less readable or obvious.\n\nApart from the [built-in Jinja template filters](http://jinja.pocoo.org/docs/2.9/templates/#builtin-filters)\nthe `any` and `all` filters are also available to evaluate conditions using \nthe Python built-in functions with the same name.\n\n## Updating the target file\n\nThe application listens for Docker *start*, *stop*, *die* and *health_status* events by \ndefault from containers and schedules an update (can be configured by the `--events` flag).\nIf the generated content didn't change and the target already has the same content\nthen the process stops.\n\nIf the template and the runtime information produces changes in the target file's\ncontent then a notification is scheduled according to the intervals set at startup.\nIf there is another notification scheduled before the minimum interval is reached\nthen it is being rescheduled unless the time since the first generation has passed\nthe maximum interval already.\nThis ensures batching notifications together in case many events arrive close to each other.\nSee the `timer.NotificationTimer` class for implementation details.\n\n## Signalling others\n\nWhen the contents of the target file have changed the application can either restart\ncontainers or send UNIX signals to them to let them know about the change.\nMatching containers is done as described on the help text of the `--restart` argument.\n\nFor example if we have a couple of containers running with the service name `nginx`\nmanaged by a Compose project, a `--signal nginx HUP` command would send a *SIGHUP*\nsignal to each of them to get them to reload their configuration.\n\nBoth of these work with Swarm when target containers might be running on different\nnodes than the app itself - using a Swarm *manager* and *workers* that alters the\nbehavior slightly.\nFor restarts, the manager app will restart matched Swarm services then stop if any of\nthem was found, otherwise the workers will execute the restarts against containers\nmatched locally.\nSignalling tasks in Swarm is not supported as far as I know, so it is always done\nusing workers that will send the signal one-by-one to containers matched locally.\n\nSee how to configure the Swarm manager and workers below.\n\n## Swarm support\n\nTo be able to execute actions as described above and to be notified of container\nevents happening on remote Swarm nodes the app can be run as a cooperating pair of\na Swarm manager and a number of Swarm workers.\nThe manager should be run as a single instance on a manager node\n(the `node.role==manager` constraint can be used when scheduling the tasks) while\nthe workers should run in `global` mode so every node in the Swarm would have one\ninstance running.\n\nCommunication between the manager and the workers is done using HTTP requests.\nThe manager uses port `9411` to accept events from the workers and those use\nport `9412` to accept action commands from the manager.\nNone of these ports have to be exposed externally, the instances will be able\nto talk to each other as long as they are on the same overlay network.\nIf the app is not running from Docker containers then these ports\nwill have to be accessible though.\n\nTo enable the Swarm manager mode on the main app, use the `--swarm-manager` flag\nalong with the `--workers` parameter that contains the hostname(s) of the workers\nto contact when executing actions.\n\nThe Swarm worker app is started using an alternative *cli* module:\n\n```text\nusage: swarm_worker.py [-h] --manager \u003cHOSTNAME\u003e [\u003cHOSTNAME\u003e ...]\n                       [--retries RETRIES] [--events \u003cEVENT\u003e [\u003cEVENT\u003e ...]]\n                       [--metrics \u003cPORT\u003e] [--debug]\n\nPyGen cli to send HTTP updates on Docker events\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --manager \u003cHOSTNAME\u003e [\u003cHOSTNAME\u003e ...]\n                        The target hostnames of the PyGen manager instances\n                        listening on port 9411\n  --retries RETRIES     Number of retries for sending an update to the manager\n  --events \u003cEVENT\u003e [\u003cEVENT\u003e ...]\n                        Docker events to watch and trigger updates for\n                        (default: start, stop, die, health_status)\n  --metrics \u003cPORT\u003e      HTTP port number for exposing Prometheus metrics\n                        (default: 9414)\n  --debug               Enable debug log messages\n```\n\nThe only required parameter is the `--manager` containing the hostname\nof the Swarm manager app listening for remote events.\n\nMy tests indicate that there can be a slight delay between a container\nbecoming healthy and the owning Swarm task changing to *running* state.\nBecause of this you might want to use the `--repeat` option of the manager\nto retry the template generation after a few seconds which should give\nsome time for the task state to settle.\n\nThe worker app is available as a Docker image too using tags prefixed with\n`worker`:\n\n- `worker-amd64` for x86 architecture\n- `worker-armhf` for 32-bits ARM\n- `worker-aarch64` for 64-bits ARM\n\nIn a similar way to the main image, the `worker` tag is a multi-arch manifest that\nwill select the appropriate worker image based on the processor architecture of the host.\n\nAn example configuration for a Swarm manager and workers in a *Composefile*\ncould be:\n\n```yaml\nversion: '3.4'\nservices:\n\n  nginx:\n    image: nginx\n    deploy:\n      replicas: 1\n      placement:\n        constraints:\n          - node.role == manager\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - /var/pygen/nginx-config:/etc/nginx/conf.d\n  \n  nginx-pygen:\n    image: rycus86/docker-pygen\n    command: \u003e\n      --template /etc/docker-pygen/templates/nginx.tmpl\n      --target /etc/nginx/conf.d/default.conf\n      --signal nginx HUP\n      --interval 3 10\n      --swarm-manager\n      --workers tasks.mystack_nginx-pygen-worker\n    deploy:\n      replicas: 1\n      placement:\n        constraints:\n          - node.role == manager\n    volumes:\n      - /var/pygen/nginx-config:/etc/nginx/conf.d\n      - /var/pygen/nginx-pygen.tmpl:/etc/docker-pygen/templates/nginx.tmpl:ro\n      - /var/run/docker.sock:/var/run/docker.sock:ro\n\n  nginx-pygen-worker:\n    image: rycus86/docker-pygen:worker\n    command: --manager mystack_nginx-pygen\n    read_only: true\n    deploy:\n      mode: global\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock:ro\n```\n\nWhen deployed using the `mystack` stack name the `nginx-pygen` manager app will\nhandle updates to the target configuration file while the `nginx-pygen-worker`\nworker apps will collect Docker events and forward it to the manager.\nThey will also take care of signalling the `nginx` container on configuration\nchange, in particular the worker app running on the same node will, the others\nwill ignore the action.\n\n## Testing\n\nThe project uses the built-in Python `unittest` library for testing.\nThe test files are in the `tests` folder and they use the `test_*.py` file name pattern.\n\nThe unit tests can be started with:\n\n```text\nPYTHONPATH=src python -m unittest discover -s tests -v\n```\n\nThe integration tests are also written in Python and use\n[Docker in Docker (dind)](https://hub.docker.com/_/docker/)\n([more information](https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/)).\nIt will start containers having the Docker daemon and start containers\ninside those to execute the tests and check the expected outcome.\n\nThe integration tests are in the same `tests` folder with the\n`it_*.py` pattern and they can be executed using:\n\n```text\nPYTHONPATH=tests python -m unittest -v integrationtest_helper\n```\n\n## Acknowledgement\n\nThis tool was inspired by the awesome [jwilder/docker-gen](https://github.com/jwilder/docker-gen)\nproject that is written in Go and uses Go templates for configuration generation.\nMany of the functionality here match or are related to what's available there.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frycus86%2Fdocker-pygen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frycus86%2Fdocker-pygen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frycus86%2Fdocker-pygen/lists"}