{"id":18929725,"url":"https://github.com/thecodingmachine/deeployer","last_synced_at":"2025-08-22T12:40:48.333Z","repository":{"id":45318779,"uuid":"246542775","full_name":"thecodingmachine/deeployer","owner":"thecodingmachine","description":"A tool to ease the creation of environments using docker-compose or Kubernetes","archived":false,"fork":false,"pushed_at":"2021-12-21T16:49:17.000Z","size":1273,"stargazers_count":14,"open_issues_count":3,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-08-18T00:38:20.065Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thecodingmachine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-11T10:33:35.000Z","updated_at":"2024-01-07T17:25:00.000Z","dependencies_parsed_at":"2022-07-19T03:02:07.333Z","dependency_job_id":null,"html_url":"https://github.com/thecodingmachine/deeployer","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/thecodingmachine/deeployer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thecodingmachine%2Fdeeployer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thecodingmachine%2Fdeeployer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thecodingmachine%2Fdeeployer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thecodingmachine%2Fdeeployer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thecodingmachine","download_url":"https://codeload.github.com/thecodingmachine/deeployer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thecodingmachine%2Fdeeployer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271641389,"owners_count":24795359,"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","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2024-11-08T11:34:39.821Z","updated_at":"2025-08-22T12:40:48.288Z","avatar_url":"https://github.com/thecodingmachine.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Deeployer\n\n## WORK IN PROGRESS\n\nDeeployer's goal is to allow you to describe an environment (be it a dev or a prod environment) in a simple JSON file.\n\nYou write a single \"deeployer.json\" file and you can deploy easily to a Kubernetes cluster, or to a single server with docker-compose.\n\nDeeployer's goal is not to be 100% flexible (you have Kubernetes for that), but rather to ease the deployment process for developers\nthat do not necessarily master the intricacies of Kubernetes.\n\nIt aims to automate a number of processes, including easy backup setup, easy reverse proxy declaration, etc...\n\n## The Deeployer config file\n\nThe Deeployer config file contains the list of containers that makes your environment:\n\n**deeployer.json**\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json\",\n  \"version\": \"1.0\",\n  \"containers\": {\n     \"mysql\": {\n       \"image\": \"mysql:8.0\",\n       \"ports\": [3306],\n       \"env\": {\n         \"MYSQL_ROOT_PASSWORD\": \"secret\"\n       }\n     },\n    \"phpmyadmin\": {\n      \"image\": \"phpmyadmin/phpmyadmin:5.0\",\n      \"host\": {\n        \"url\": \"phpmyadmin.myapp.localhost\",\n        \"containerPort\": 80\n      },\n      \"env\": {\n        \"PMA_HOST\": \"mysql\",\n        \"MYSQL_ROOT_PASSWORD\": \"secret\"\n      }\n    }\n  }\n}\n```\n\nTODO: add volumes when ready\n\nLet's have a closer look at this file.\n\nThe first line is optional:\n\n```\n  \"$schema\": \"https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json\",\n```\n(TODO: migrate the URL to a static website)\n\nIt declares the JsonSchema. We highly recommend to keep this line. Indeed, if you are using an IDE like Visual Studio\nCode or a JetBrain's IDE, you will get auto-completion and validation of the structure of the file right in your IDE!\n\nThen, the \"containers\" section contains the list of containers for your environment.\nIn the example above, we declare 2 containers: \"mysql\" and \"phpmyadmin\".\nJust like in \"docker-compose\", the name of the container is also an internal DNS record. So from any container of your\nenvironment, the \"mysql\" container is reachable at the \"mysql\" domain name. \n\nFor each container, you need to pass:\n\n- \"image\": the Docker image for the container\n- \"ports\": a list of ports this image requires. Warning! Unlike in `docker-compose`, this is not a list of ports that\n  will be shared with the host. This is simply a list of ports this image opens. This is particularly important if you\n  do deployments in Kubernetes (each port will be turned into a K8S service).\n\nYou can pass environment variables using the \"env\" key:\n\n```json\n\"env\": {\n \"MYSQL_ROOT_PASSWORD\": \"secret\"\n}\n```\n\nWe will see later how to manage those secrets without storing them in full text. (TODO)\n \n\n## Using Jsonnet\n\nJSON is not the only format supported for the \"Deeployer\" config file. You can also write the file in [Jsonnet](https://jsonnet.org/learning/tutorial.html).\n\nJsonnet? This is a very powerful data templating language for JSON.\n\nBy convention, you should name your Deeployer file `deeployer.libsonnet`. (TODO: switch to `deeployer.jsonnnet`)\n\nHere is a sample file:\n\n**deeployer.libsonnet**\n```jsonnnet\n{\n  local mySqlPassword = \"secret\",\n  local baseUrl = \"myapp.localhost\",\n  \"$schema\": \"https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json\",\n  \"version\": \"1.0\",\n  \"containers\": {\n     \"mysql\": {\n       \"image\": \"mysql:8.0\",\n       \"ports\": [3306],\n       \"env\": {\n         \"MYSQL_ROOT_PASSWORD\": mySqlPassword\n       }\n     },\n    \"phpmyadmin\": {\n      \"image\": \"phpmyadmin/phpmyadmin:5.0\",\n      \"host\": {\n         \"url\": \"phpmyadmin.\"+baseUrl\n         \"containerPort\": 80\n      },\n      \"env\": {\n        \"PMA_HOST\": \"mysql\",\n        \"MYSQL_ROOT_PASSWORD\": mySqlPassword\n      }\n    }\n  }\n}\n```\n\nIn the example above, we declare 2 variables and use these variables in the config file. See how the `mySqlPassword`\nvariable is used twice? Jsonnet allows us to avoid duplicating configuration code in all containers.\n\nBut there is even better! Let's assume you have a staging and a production environment. Maybe you want PhpMyAdmin on the\nstaging environment (for testing purpose) but not on the production environment. Using Jsonnet, we can do this easily\nusing 2 files:\n\n**deeployer.libsonnet**\n```jsonnnet\n{\n  local mySqlPassword = \"secret\",\n  \"$schema\": \"https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json\",\n  \"version\": \"1.0\",\n  \"containers\": {\n     \"mysql\": {\n       \"image\": \"mysql:8.0\",\n       \"ports\": [3306],\n       \"env\": {\n         \"MYSQL_ROOT_PASSWORD\": mySqlPassword\n       }\n     }\n  }\n}\n```\n\n**deeployer-dev.libsonnet**\n```jsonnnet\nlocal prod = import \"deeployer.libsonnet\";\nlocal baseUrl = \"myapp.localhost\";\nprod + {\n  \"containers\"+: {\n    \"phpmyadmin\": {\n      \"image\": \"phpmyadmin/phpmyadmin:5.0\",\n      \"host\": {\n        \"url\": \"phpmyadmin.\"+baseUrl,\n        \"containerPort\": 80\n      },\n      \"env\": {\n        \"PMA_HOST\": \"mysql\",\n        \"MYSQL_ROOT_PASSWORD\": prod.containers.mysql.env.MYSQL_ROOT_PASSWORD\n      }\n    }\n  }\n}\n```\n\nTODO: test this.\n\n\n## Referencing environment variables in the Deeployer config file\n\nWhen doing continuous deployment, it is common to put environment dependant variables and secrets in environment\nvariables. Deeployer can access environment variables using the Jsonnet \"env\" external variable:\n\n**deeployer.libsonnet**\n```jsonnnet\nlocal env = std.extVar(\"env\");\n{\n  local mySqlPassword = \"secret\",\n  \"version\": \"1.0\",\n  \"containers\": {\n     \"mysql\": {\n       \"image\": \"mysql:8.0\",\n       \"ports\": [3306],\n       \"env\": {\n         \"MYSQL_ROOT_PASSWORD\": env.MYSQL_PASSWORD\n       }\n     }\n  }\n}\n```\n\nThe first line is putting all environments variables in the `env` local variable:\n\n```jsonnet\nlocal env = std.extVar(\"env\");\n```\n\nThen, you can access all environment variables from the machine running Deeployer using `env.ENV_VARIABLE_NAME`.\n\nBeware! If the environment variable is not set, Jsonnet will throw an error!\n\n### Enabling HTTPS\n\nDeeployer offers HTTPS support out of the box using Let's encrypt.\n\n**deeployer.json**\n```json\n{\n  \"version\": \"1.0\",\n  \"$schema\": \"https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json\",\n  \"containers\": {\n    \"phpmyadmin\": {\n      \"image\": \"phpmyadmin/phpmyadmin:5.0\",\n      \"host\": {\n        \"url\": \"phpmyadmin.myapp.localhost\",\n        \"containerPort\": 80,\n        \"https\": \"enable\"\n      },\n      \"env\": {\n        \"PMA_HOST\": \"mysql\"\n        \"MYSQL_ROOT_PASSWORD\": \"secret\"\n      }\n    }\n  },\n  \"config\": {\n    \"https\": {\n      \"mail\": \"mymail@example.com\"\n    }\n  }\n}\n```\n\nIn order to automatically get a certificate for your HTTPS website, you need to:\n\n- Add `\"https\": \"enable\"` in your `host` section\n- At the bottom of the `deeployer.json` file, add a \"config.https.mail\" entry specifying a mail address. This mail address\n  will be used to warn you, should something goes wrong with the certificate (for instance if the certificate is going\n  to expire soon)\n\nPlease note that if you are using Kubernetes, you will need in addition to install CertManager in your cluster.\n[See the relevant Kubernetes documentation below](#configuring-your-kubernetes-cluster-to-support-https) \n\n### Customizing Kubernetes resources\n\nDeeployer's goal is to allow you to describe a complete environment in a simple JSON file. It simplifies a lot the \nconfiguration by making a set of common assumptions on your configuration. Of course, the JSON config file does not\nlet you express everything you can in a raw Kubernetes environment. This is by design.\n\nHowever, there are times when you might need a very specific K8S feature. In this case, you can use JSONNET functions\nto dynamically alter the generated K8S configuration files.\n\nTo do this, you will need to use a `deeployer.libsonnet` configuration file instead of a `deeployer.json` configuration\nfile.\n\nYou can then use the hidden `config.k8sextension` field to alter the generated configuration.\nIn the example below, we are adding 2 annotations to the container of the deployment:\n\n```libsonnet\n{\n  \"version\": \"1.0\",\n  \"containers\": {\n    \"phpmyadmin\": {\n      \"image\": \"phpmyadmin\",\n      \"ports\": [\n        80\n      ],\n      \"host\": {\n        \"url\": \"myhost.com\"\n      }\n    }\n  },\n  \"config\": {\n    k8sextension(k8sConf)::\n      k8sConf + {\n        phpmyadmin+: {\n          deployment+: {\n            spec+: {\n              template+: {\n                metadata+: {\n                  annotations+: {\n                    \"prometheus.io/port\": \"8080\",\n                    \"prometheus.io/scrape\": \"true\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n  }\n}\n``` \n\nWhat is going on here? We are describing in the config a `k8sextension` function.\nThis JSONNET function is passed a JSON object representing the complete list of all the Kubernetes resources.\nUsing JSONNET, we extend that list to add annotations in one given container.\n\nGood to know:\n\nResources stored in the JSON config object passed to `k8sextension` is on two levels. \n\n- The first level is the name of the container (`phpmyadmin` in the example above)\n- The second level is the name of the resource type we want to target (here, a `deployment`)\n\n## Usage\n\n### Deploying using Kubernetes\n\nView the list of Kubernetes resources that will be generated using `deeployer-k8s show`\n\n```console \n$ deeployer-k8s show\n```\n\nBy default, Deeployer will look for a `deeployer.libsonnet` or a `deeployer.json` file in the current working directory.\n\nYou can specify an alternative name in the command:\n\n```console \n$ deeployer-k8s show deeployer-dev.jsonnet\n```\n\nThe \"show\" command is only used for debugging. In order to make an actual deployment, use the \"apply\" command:\n\n```console \n$ deeployer-k8s apply --namespace target-namespace\n```\n\nImportant: if you are using Deeployer locally, Deeployer will not use your Kubectl config by default. You need to pass\nthe Kubectl configuration as an environment variable.\n\nFinally, you can delete a complete namespace using:\n\n```console \n$ deeployer-k8s delete --namespace target-namespace\n```\n\nThis is equivalent to using:\n\n```console \n$ kubectl delete namespace target-namespace\n```\n\n#### Connecting to a \"standard\" environment\n\nIf a \"kubeconfig\" file is enough to connect to your environement, you can connect to your cluster\nby setting the `KUBE_CONFIG_FILE` environment variable.\n\n- `KUBE_CONFIG_FILE` should contain the content of the *kubeconfig* file.\n\n#### Connecting to a GCloud environment\n\nYou can connect to a GKE cluster by setting these environment variables:\n\n- `GCLOUD_SERVICE_KEY`\n- `GCLOUD_PROJECT`\n- `GCLOUD_ZONE`\n- `GKE_CLUSTER`\n\n#### Configuring your Kubernetes cluster to support HTTPS\n\nIn order to have HTTPS support in Kubernetes, you need to install [Cert Manager](https://cert-manager.io/) in your Kubernetes cluster.\nCert Manager is a certificate management tool that acts **cluster-wide**. Deeployer configures Cert Manager to generate\ncertificates using [Let's encrypt](https://letsencrypt.org/).\n\nYou can install Cert Manager using [their installation documentation](https://cert-manager.io/docs/installation/kubernetes/).\nYou do not need to create a \"cluster issuer\" as Deeployer will come with its own issuer.\n\nYou need to install Cert Manager v0.11+.\n\n#### Preventing automatic redeployment\n\nBy default, in Kubernetes, all pods will be halted, and restarted. Even if the configuration did not change (Deeployer tries to redownload the latest version of the image).\nBut in the case of some services (like a MySQL database or a Redis server), stopping and restarting the service will cause\na disruption, for no good reason.\n\nYou can tell Deeployer to not restart a pod automatically using the \"redeploy\": \"onConfigChange\" option.\n\n**deeployer.json**\n```json\n{\n  \"version\": \"1.0\",\n  \"$schema\": \"https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json\",\n  \"containers\": {\n    \"mysql\": {\n      \"image\": \"mysql:8.0\",\n      \"ports\": [3306],\n      \"env\": {\n        \"MYSQL_ROOT_PASSWORD\": \"secret\"\n      },\n      \"redeploy\": \"onConfigChange\"\n    }\n  }\n}\n```\n\n\nWith `\"redeploy\": \"onConfigChange\"`, your pod will be changed only if the configuration is changed.\n\n#### Redeployment strategy\n\nIn Kubernetes, by default, Deeployer will stop and recreate the pod if there is only one pod (if you did not set the `replicas` property).\nIf you configured `replicas` to a value greater than 1, Deeployer will use a \"RollingUpdate\" for this pod.\n\n### Deploying using docker-compose\n\nTo deploy with deeployer-compose, you need to setup the following alias on your local machine since deeployer in its first versions is only accessible thanks to its official docker-image :\n\n```\nalias deeployer-compose=\"docker run --rm -it -e \\\"JSON_ENV=\\$(jq -n env)\\\" -v $(pwd):/var/app -v /var/run/docker.sock:/var/run/docker.sock thecodingmachine/deeployer:latest deeployer-compose\"\n```\n\n\n## Installing locally\n\nIn order to use Deeployer locally, you need to install:\n\n- [Docker](https://docs.docker.com/get-docker/)\n- [jq](https://stedolan.github.io/jq/download/)\n\nDeeployer can be run via Docker. Installation is as easy as adding a few aliases to your `~/.bashrc` (if you are using Bash)\n\n`~/.bashrc`\n```console\nalias deeployer-k8s='docker run --rm -it -e \"JSON_ENV=\\$\\(jq -n env\\)\" -v $(pwd):/var/app thecodingmachine/deeployer:latest deeployer-k8s'\nalias deeployer-compose='docker run --rm -it -e \"JSON_ENV=\\$\\(jq -n env\\)\" -v $(pwd):/var/app -v /var/run/docker.sock:/var/run/docker.sock thecodingmachine/deeployer:latest deeployer-compose'\nalias deeployer-self-update=\"docker pull thecodingmachine/deeployer:latest\"\n```\n\nDeeployer is under heavy development. Do not forget to update the Docker image regularly:\n\n```console\n$ deeployer-self-update\n```\n\n## Usage in Gitlab CI\n\nTo use deeployer in gitlab ci, you'll need to specify in your .gitlab-ci.yml file a job for the deployment like in the following example :\n```\ndeeploy:\n  image: thecodingmachine/deeployer:latest\n  stage: deploy\n  variables:\n    KUBE_CONFIG_FILE: ${KUBE_CONFIG}\n  script:\n    - deeployer-k8s apply --namespace ${CI_PROJECT_PATH_SLUG}-${CI_COMMIT_REF_SLUG}\n    - curl \"https://bigbro.thecodingmachine.com/gitlab/call/start-environment?projectId=${CI_PROJECT_ID}\u0026commitSha=${CI_COMMIT_SHA}\u0026ref=${CI_COMMIT_REF_NAME}\u0026name=${CI_PROJECT_PATH_SLUG}-${CI_COMMIT_REF_SLUG}\"\n  environment:\n    name: review/$CI_COMMIT_REF_NAME\n    url: https://bigbro.thecodingmachine.com/environment/${CI_PROJECT_PATH_SLUG}-${CI_COMMIT_REF_SLUG}\n  when: manual\n  only:\n    - /^CD-.*$/\n```\nthe image thecodingmachine/deeployer:latest needs a variable named KUBE_CONFIG_FILE which contains the kubernetes config file that gives you access to the cluster. In the case of the example we setted it as a CI/CD variable of gitlab since the feature is still available.\n\n```\n  variables:\n    KUBE_CONFIG_FILE: ${KUBE_CONFIG}\n```\n\nNext, in the script section of the job we just use the command apply of deeployer-k8s with the mandatory option --namespace which in the case of the example is setted thanks to CI/CD variables.\nSince Deeployer is bundled as a Docker image, usage in Gitlab CI is very easy (assuming you are using a Docker based Gitlab CI runner, of course);\n\n**.gitlab-ci.yaml**\n```yaml\nstages:\n  # Your other stages ...\n  - deploy\n  - cleanup\n\ndeploy_branches:\n  image: thecodingmachine/deeployer:latest\n  stage: deploy\n  script:\n    - deeployer-k8s apply --namespace ${CI_PROJECT_PATH_SLUG}-${CI_COMMIT_REF_SLUG}\n  environment:\n    name: review/$CI_COMMIT_REF_NAME\n    url: https://${CI_COMMIT_REF_SLUG}.${CI_PROJECT_PATH_SLUG}.test.yourapp.com\n    on_stop: cleanup_branches\n  only:\n    - branches\n\ncleanup_branches:\n  stage: cleanup\n  image: thecodingmachine/deeployer:latest\n  variables:\n    GIT_STRATEGY: none\n  script:\n    - deeployer-k8s delete --namespace ${CI_PROJECT_PATH_SLUG}-${CI_COMMIT_REF_SLUG}\n  when: manual\n  environment:\n    name: review/$CI_COMMIT_REF_NAME\n    action: stop\n  only:\n    - branches\n  except:\n    - master\n```\n\nFor this to work, you will need to put the content of your Kubernetes configuration file in a `KUBE_CONFIG_FILE` environment variable in your project on Gitlab.\n\n### Deploying in a Google cloud Kubernetes cluster using Gitlab CI\n\nIf you are connecting to a Google Cloud cluster, instead of passing a `KUBE_CONFIG_FILE`, you will need to pass\nthis set of environment variables:\n\n- `GCLOUD_SERVICE_KEY`\n- `GCLOUD_PROJECT`\n- `GCLOUD_ZONE`\n- `GKE_CLUSTER`\n\n## Usage in Github actions\n\nDeeployer comes with a Github action.\n\n**deploy_workflow.yaml**\n```yaml\nname: Deploy Docker image\n\non:\n  - push\n\njobs:\n  deeploy:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      - name: Deploy\n        uses: thecodingmachine/deeployer-action@master\n        env:\n          KUBE_CONFIG_FILE: ${{ secrets.KUBE_CONFIG_FILE }}\n        with:\n          namespace: target-namespace\n```\n\nYou will need to put the content of your Kubernetes configuration file in the `KUBE_CONFIG_FILE` secret on Github.\n\n### Deploying using the Github action in a Google cloud Kubernetes cluster\n\nIf you are connecting to a Google Cloud cluster, instead of passing a `KUBE_CONFIG_FILE`, you will need to pass\nthis set of environment variables:\n\n- `GCLOUD_SERVICE_KEY`\n- `GCLOUD_PROJECT`\n- `GCLOUD_ZONE`\n- `GKE_CLUSTER`\n\n## Passing registry credentials\n\nIf you are using a private registry to store your Docker images, Deeployer needs the credentials to this registry in order\nto deploy images successfully.\n\nPut the credentials to your images in the \"config\" section:\n\n**deeployer.json**\n```\n{\n  \"config\": {\n    \"registryCredentials\": {\n      \"registry.example.com\": {\n        \"user\": \"my_user\",\n        \"password\": \"my_password\"\n      }\n    }\n  }\n}\n```\n\nPlease note that the key of the \"registryCredentials\" object is the URL to your Docker private registry.\n\nThese will be automatically passed to Kubernetes that will create a \"registry secret\".\n\n## Contributing\n\nDownload and install the Jsonnet Bundler: https://github.com/jsonnet-bundler/jsonnet-bundler/releases\n\nInstall the dependencies:\n\n```bash\n$ jb install\n```\n\nDownload and install Tanka: https://github.com/grafana/tanka/releases\n\nDownload and install Kubeval: https://kubeval.instrumenta.dev/installation/\n\nDownload and install AJV:\n\n```bash\n$ sudo npm install -g ajv-cli@^5\n$ sudo npm install -g ajv-formats@^2.1.1\n```\n\nDownload and install Jsonlint:\n\n```bash\n$ sudo npm install -g jsonlint\n```\n\nBefore submitting a PR:\n\n- run the tests:\n  ```console\n  cd tests/\n  ./run_tests.sh\n  ```\n- run the linter:\n  ```console\n  ./lint.sh\n  ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthecodingmachine%2Fdeeployer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthecodingmachine%2Fdeeployer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthecodingmachine%2Fdeeployer/lists"}