{"id":42369728,"url":"https://github.com/v3io/manof","last_synced_at":"2026-01-27T19:19:34.363Z","repository":{"id":38838675,"uuid":"135575706","full_name":"v3io/manof","owner":"v3io","description":"Manof - The jew crane","archived":false,"fork":false,"pushed_at":"2025-04-09T10:33:23.000Z","size":123,"stargazers_count":14,"open_issues_count":1,"forks_count":21,"subscribers_count":7,"default_branch":"development","last_synced_at":"2025-04-09T11:31:46.551Z","etag":null,"topics":["crane","docker","docker-commands","dockerfile","jew-crane","provision","python","tool","usability"],"latest_commit_sha":null,"homepage":"https://www.iguazio.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/v3io.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":"2018-05-31T11:39:07.000Z","updated_at":"2025-04-09T10:33:20.000Z","dependencies_parsed_at":"2024-11-17T09:23:36.070Z","dependency_job_id":"76e6ef6c-8c74-4c8e-966d-cfdad66d8cb4","html_url":"https://github.com/v3io/manof","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/v3io/manof","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v3io%2Fmanof","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v3io%2Fmanof/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v3io%2Fmanof/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v3io%2Fmanof/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/v3io","download_url":"https://codeload.github.com/v3io/manof/tar.gz/refs/heads/development","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/v3io%2Fmanof/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28819210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T18:44:20.126Z","status":"ssl_error","status_checked_at":"2026-01-27T18:44:09.161Z","response_time":168,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["crane","docker","docker-commands","dockerfile","jew-crane","provision","python","tool","usability"],"created_at":"2026-01-27T19:19:32.626Z","updated_at":"2026-01-27T19:19:34.352Z","avatar_url":"https://github.com/v3io.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n\n# Manof\n\nThe jew crane\n\n# Overview\n\n## What it is\n\nManof is a tool for building and running multi-container Docker applications, similar to \n[Crane](https://github.com/michaelsauter/crane) and [Docker compose](https://docs.docker.com/compose/) \n(if it supported build). Manof takes an imperative approach where configuration is expressed through \n\u003ci\u003ePython code\u003c/i\u003e instead of static configuration files (json/yaml). Rather than have several mostly duplicated \n`.yml` files, users leverage everything in the Python arsenal to describe their environments in a single \n`manofest.py` python module.\u003cbr\u003e\nManof itself is Python-based ([Twisted](https://twistedmatrix.com/)). \n \n## A short example\n\n1. Create a small `manofest.py` file (Or use the one in `examples/basic/manofest.py`):\n    ```python\n    \n    import manof\n    \n    \n    class MobyBase(manof.Image):\n    \n        @property\n        def detach(self):\n            return False\n    \n        @property\n        def rm_on_run(self):\n            return True\n    \n        @property\n        def labels(self):\n            return {\n                'manofest-class': self.name,\n            }\n    \n        @property\n        def env(self):\n            return [\n                {'VERSE': 'Plain talking. Take us so far.'},\n            ]\n    \n        @property\n        def command(self):\n            return '{0} \"echo \\'{1}\\'\"'.format(self.shell_cmd, self.chorus_line)\n    \n        @property\n        def shell_cmd(self):\n            raise RuntimeError('Unknown shell')\n    \n        @property\n        def chorus_line(self):\n            return None\n    \n    \n    class MobyUbuntu(MobyBase):\n    \n        @property\n        def image_name(self):\n            return 'ubuntu:16.04'\n    \n        @property\n        def shell_cmd(self):\n            return '/bin/bash -c'\n    \n        @property\n        def chorus_line(self):\n            return 'Lift me up, lift me up'\n    \n        @property\n        def exposed_ports(self):\n            return [\n                8000,\n            ]\n    \n        @property\n        def env(self):\n            return super(MobyUbuntu, self).env + [\n                {'MY_CUSTOM_ENV': 'VALUE_A'},\n            ]\n    \n    \n    class MobyAlpine(MobyBase):\n    \n        @property\n        def image_name(self):\n            return 'alpine:3.7'\n    \n        @property\n        def shell_cmd(self):\n            return '/bin/sh -c'\n    \n        @property\n        def chorus_line(self):\n            return 'Higher now ama'\n    \n        @property\n        def exposed_ports(self):\n            return [\n                {9000: 9001},\n            ]\n    \n        @property\n        def env(self):\n            return super(MobyAlpine, self).env + [\n                {'MY_CUSTOM_ENV': 'VALUE_B'},\n            ]\n    ```\n\n2. Now, Try running this from the same dir as the above `manofest.py` (or point to it using the appropriate argument), \nto get a feeling of what manof provides for you:\n     ```\n     \u003e manof lift moby_ubuntu\n     ```\n    You'll be getting a log output to your stdout, stating the phases of building and running the image, ending \n    with a log line similar to this one:\n    ```\n     07.07.18 22:12:09.918 manof.moby_ubuntu: (I) Command succeeded {\"cwd\": \"None\", \"err\": \"\", \"out\": \"Lift me up, lift me up\"}\n    {\"command\":\n       \"docker run --rm --net host --label manofest-class=moby_ubuntu --publish\n       8000:8000 --env VERSE='Plain talking. Take us so far.' --env\n       MY_CUSTOM_ENV=VALUE_A --name moby_ubuntu ubuntu:16.04 /bin/bash -c \"echo\n       'Lift me up, lift me up'\"\"}\n     ```\n3. Now, try this one, and inspect its output:\n     ```\n     \u003e manof lift moby_alpine\n     ```\n4. Lifting both, consecutively:\n     ```\n     \u003e manof lift moby_ubuntu moby_alpine\n     ```\n5. If you'd like to separately provision (build) and run the images:\n     ```\n     \u003e manof provision moby_ubuntu moby_alpine\n     \u003e manof run moby_ubuntu moby_alpine\n     ```\n    \nAs you can see, Your long and unreadable docker commands will turn into digestible, wieldly `manof` \nequivalents. Manof can manage almost all aspects of building/running docker images and volumes in a very friendly and \nflexible way.\nAll is achieved by programming the behaviour of your project's images and volumes (groups supported) into a \nconfiguration-like python module (by convention called `manofest.py`), and reading this in runtime, the `manof` CLI will \ngenerate the necessary docker commands for the defined targets.\u003cbr\u003e\n\n\u003e \u003ci\u003eThis approach provides a quantum leap in flexibility and re-usability of the different properties' logic definition. \nFor example, maintaining a single `manofest.py` file per project, can answer the needs of many deployment \nscenarios - different host os's, environments (dev/integration and production), app versions, users, etc...\u003c/i\u003e\n\n## How it came to be\n\n\u003e \u003ci\u003e\"Another docker orchestration tool?!\" - a (longer) real-life example\u003c/i\u003e\n\nA long time ago, we were using [Crane](https://github.com/michaelsauter/crane) in [Iguazio](https://www.iguazio.com).\nBut shortly after we found ourselves with a plethora of different `crane.yml` variants for different\nscenarios - mac/ubuntu/centos, for dev/production and different integration scenarios. Each scenario\nhad some (sometime different) subset of building/running docker options which would need to be tweaked.\u003cbr\u003e\n\nManaging different configurations was just too cumbersome and did not allow for an elegant code-like config \nsharing between the different configurations.\u003cbr\u003e\n\nWe figured would be great if we could just define it in python code, inspecting machine properties or importing \nwhatever we need as we go along to define a certain volume or port forwarding rule, and being able to decide which \ndocker arguments are \"locked in\" for specific images (or volumes) and which should \u003ci\u003ereally\u003c/i\u003e be decided on \nruntime, for our use cases.\u003cbr\u003e\nWe wanted a smart grouping system, so we can run `manof provision group_a` and each image in the group will be built \nwith its needed arguments and logic, and the build should consider the relations and dependencies between the images, \nbuilding them in the right order.\u003cbr\u003e\nWe also would like to have a smart dependency system - image `a` should be able to be based off of image `b`, but \nstill inherit some run/build behaviour (like environment variables or ports exposure) from image `c`, or even just \nsome generic abstract class `d`. In other words, we want the power of object inheritance. \n\nSo, here we are then... introducing `manof`!\n\n- Let's look at a very obvious example. I present you this horrible docker command, \nwhich is a slightly treated version of a real world use case:\n\n        docker run --detach --net bridge --log-driver none --label\n        services=igz8.identity.7 --label igz-project=platform --label\n        service_group=standalone --health-cmd=\u003cour shell healthcheck cmd\u003e\n        --health-interval=15s --health-retries=3 --health-timeout=5s --publish 3345:2345\n        --volume host_path_1:container_path_1\n        --volume host_path_2:container_path_2\n        ...\n        (more volume args)\n        ...\n        --env IGZ_CLUSTER_NAME=igzc0 --env IGZ_NODE_NAME=igz8 --env\n        IGZ_SERVICE_NAME=identity --env IGZ_SERVICE_INSTANCE=7 --env\n        IGZ_SOME_IP=11.22.34.55 --env IGZ_SOME_PORT=8001\n        IGZ_SOME_ENV_1=value_1 --env IGZ_SOME_ENV_2=value_2\n        --env IGZ_SOME_ENV_3=value_3\n        ...\n        (soooo many more env vars)\n        ...\n        --name igz8.identity.7 iguazio/identity\n\nInstead of the above, you can run this compact and equivalent `manof` command, given the `identity` image is properly \nconfigured in our (internal, of course) `manofest.py` file.\n\n        manof run --node-name=igz8 --service-instance=7 -po=1000 identity\n\nNow, that's much more friendly to run now, isn't it?! :relaxed: \nThe resulting `docker` command will be exactly the same.\n\n## Manof core principles\n\n- Declare images, volumes, and groups of the images/volumes using the strength of a full-fledged programming \nlanguage (python) - including inheritance, conditions, flow control, code re-usability etc... This will give each its own\ncustom behaviour for building/running/removing and so on.\n\n- Define whichever image specific argument you want these images to have on the manof CLI, \nbecause some things should be overridable at run time! (like `--service-instance` in the [example above](#how-it-came-to-be)).\n\n- Now, you can run common docker commands in a compact manner, which will generate a rich `docker command` \nin a smart manner, giving you 100% control over everything with either the `manofest.py` module, or a combination of\nthe `manofest.py` and run time arguments to the `manof` tool.\n\n- Of course, it is possible to create and maintain as many `manofest.py` files as you'd like, if you are so inclined, \neven though, from our experience, we recommend keeping it to 1 per repo/project, for a seamless day-to-day experience.\nIn a multiple manofest files scenario, don't forget to point your `manof` to the correct `manofest.py` file \nwhen invoking it, using the `-mp/--manofest-path` cli switch.\n\n- Similarly and consistently with [crane](https://github.com/michaelsauter/crane):\n    - Building/pulling target images and volumes is referred to by the verb `provision`\n    - Provisioning and then running an image in a single command is referred to by verb `lift`\n\n\n## Command structure\n\n    manof [manof args ...] {operation} [operation-specific-args ...] [target-specific args ...] targets [targets ...]\n\n\n- Currently supported operations:\n`{update,provision,run,stop,rm,lift,serialize,push,pull}`\n    \n- Commonly used `manof args`:\n  \n  - `--manofest-path MANOFEST_FILEPATH` - Explicitly provide the full path to the `manofest.py` file, otherwise a \n  `manofest.py` will be expected to reside in CWD. \n  \n  - `--log-severity {verbose,debug,info,warn,warning,error,V,D,I,W,E}` - Setting the log severity of both console logging, \n  and file logging (if such is configured). Accepts both full and abbreviation notation. \n  Can be controlled separately via `--log-console-severity` and `--log-file-severity`. \n  \n  - `--parallel NUM` - How many docker commands should be launch simultaneously (default is 1).\n  \n  - `--dry-run` - Tells manof to not run any docker command, just log. Can be useful for debugging.\n\n  - We are continuously updating and improving manof. Support for new options is being added constantly. \n  Please type `manof --help` for a full list of possible arguments \n  and options.\n\n  - Update your manof using `manof update` which will pull latest code from `github`.\n\n\n# Pre-requisites\n\n- [python 3.7](https://www.python.org/downloads/)\n- [pip](https://pip.pypa.io/en/stable/installing/)\n- [virtualenv](https://virtualenv.pypa.io/en/stable/installation/)\n- [docker](https://docs.docker.com/install/) (well, duh) - Any docker-CE/EE version would do. \nOlder version are partially supported (some flags/properties may not work)\n\n\n# Installation\n      \n    \u003e git clone git@github.com:v3io/manof.git\n    \u003e cd manof \u0026\u0026 ./install\n\n# More Examples\n\n\n## Basic Usage\n\nHere we provide some examples of basic `manof` usage, to show you the ropes of defining and manipulating \nimages, volumes and groups thereof.\nThe manofest code for the below examples, in its entirety, can be found in `examples/basic/manofest.py`.\nHere we will focus on specific code snippets to demonstrate key manof building blocks.\n\n- Using manof to provision and run an image:\n    - Manof commands:\n    ```\n    manof lift image_a\n    ```\n    - Example `manofest.py` code. Here we threw in some basic properties for the image:\n        ```python\n        import manof\n        \n        \n        class ImageA(manof.Image):\n        \n            @property\n            def image_name(self):\n                return 'ubuntu:16.04'\n        \n            @property\n            def detach(self):\n                return False\n        \n            @property\n            def labels(self):\n                return {\n                    'my-project': 'custom_image_1',\n                }\n            \n            @property\n            def exposed_ports(self):\n                return [\n                    8000,\n                    {9000: 9001},\n                ]\n            @property      \n            def env(self):\n                return [\n                  'MY_ENV_1',\n                  {'MY_ENV_2': 'TARGET_VALUE_1'},\n                ] \n        \n            @property\n            def command(self):\n                return '/bin/bash -c \"echo \\'hello manof user\\'\"'\n        \n        ```\n\n- Using manof to provision and run a group of images:\n    \n    - Manof commands:\n    ```\n    manof lift my_images\n    ```\n    - Example `manofest.py` code:\n\n        ```python\n        import manof\n        \n        \n        class MyImages(manof.Group):\n        \n            @property\n            def members(self):\n                return [\n                    'ImageA',\n                    'ImageB',\n                ]\n        ```\n\n- Using manof to create a named volume:\n    \n    - Manof commands:\n    ```\n    manof provision --node-name=node3 volume_a\n    ```\n    - Example `manofest.py` code. Here we also define a volume specific argument:\n    \n        ```python\n        import datetime\n        import pytz\n        \n        import manof\n        \n        class VolumeA(manof.NamedVolume):\n        \n            def register_args(self, parser):\n                parser.add_argument('--node-name', type=str, default='node0')\n        \n            @property\n            def prefix(self):\n                \"\"\"\n                Here we use the argument --node-name to affect a prefix. This will prefix the actual named-volume name\n                    as can be seen using 'docker volume ls'\n                \"\"\"\n                return 'proj_a_{0}_'.format(self._args.node_name)\n        \n            @property\n            def labels(self):\n                return {\n                    'creation_datetime': datetime.datetime.now(pytz.utc).isoformat(),\n                    'volume_image': self.name,\n                }\n        ```\n\n- Using manof to create a group of named volumes:\n    \n    - Manof commands:\n    ```\n    manof provision my_volumes\n    ```\n    - A basic volume group example in `manofest.py`:\n    \n        ```python\n        import manof\n        \n        class MyVolumes(manof.Group):\n        \n            @property\n            def members(self):\n                return [\n                    'VolumeA',\n                    'VolumeB',\n                ]\n        ```\n  \n## Advanced usage\n\nMore example manofest files showing off some common use patterns will be added soon to the repository\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fv3io%2Fmanof","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fv3io%2Fmanof","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fv3io%2Fmanof/lists"}