In the beginning there was silence and darkness - by Midjourney
Docker-Compose files declare the state of a Docker stack and are used to configure Docker Containers. In this Blog I will show you how to edit them with Python, so you can change them programmatically.
Packages for editing YAML
Docker-Compose files are written in YAML, but unlike JSON Pythons Standard Library does not come with a parser for YAML. But there are some in the Python Package Index. My first instinct was PyYAML but I does come with a downside: It sorts the keys alphabetically in the output. This means that the
Version of the Docker-Compose file would be at the bottom of the file. This behaviour is can be changed in Version 5.1 of PyYAML:
yaml.dump(data, default_flow_style=False, sort_keys=False).
There is also ruamel.yaml which saves some roundtrips while emitting YAML files and thus leaves the structure of the file mostly intact. Thus I decided to give it a try.
Example: Changing the Version of an Image
This example shows how to change the Version of a Docker Image with a little commandline script. The script takes a path and a version as positional arguments
import argparse import os import ruamel.yaml yaml = ruamel.yaml.YAML() yaml.preserve_quotes = True yaml.indent(sequence=3, offset=1) def get_docker_compose_files(path): """ Walks the given path and collects all docker-compose.* files and yields """ for dirpath, dirnames, files in os.walk(path): for file in files: if 'docker-compose.' in file: yield os.path.join(dirpath, file) def change_version(version, path): """ Changes the Version of the Example Service Note: Example could be a good candidate for an additional commandline argument """ docker_compose_files = get_docker_compose_files(path) for docker_compose in docker_compose_files: with open(docker_compose, 'r') as ymlfile: docker_config = yaml.load(ymlfile) image = docker_config['services']['example']['image'] string_split = image.split(':') string_split.pop() string_split.append(version) new_image_string = ':'.join(string_split) docker_config['services']['example']['image'] = new_image_string with open(docker_compose, 'w') as newconf: yaml.dump(docker_config, newconf) def main(): parser = argparse.ArgumentParser() parser.add_argument("version") parser.add_argument('path') args = parser.parse_args() version = args.version path = args.path change_version(version, path) if __name__ == '__main__': main()
The resulting docker-compose file will look almost the same as the input file and this keeps standard conventions like devlaring the version first and the networks or volumes last.
If you have suggestions or spottet an error send me an email via firstname.lastname@example.org