{"id":1150,"date":"2019-08-06T16:41:00","date_gmt":"2019-08-06T11:11:00","guid":{"rendered":"http:\/\/blogs.opstree.com\/?p=1150"},"modified":"2019-09-19T11:45:31","modified_gmt":"2019-09-19T06:15:31","slug":"docker-compose-as-a-bundled-application","status":"publish","type":"post","link":"https:\/\/opstree.com\/blog\/2019\/08\/06\/docker-compose-as-a-bundled-application\/","title":{"rendered":"Docker-Compose As A Bundled Application"},"content":{"rendered":"\n<p> When docker was released as a new containerization tool, it took the market by a storm. With its lightweight images, multi-os support, and ability to ship containers, it\u2019s popularity only roared. I have been using it for more than six months now, I can see why it is so. Hypervisors, another type of virtualizing tools,\u00a0 have been hard on hardware. Which means they require a lot of resources to run. This increases the cost of running applications way more than those running on containers. This is the problem docker solved and hence, it\u2019s popularity. Docker engine just sits on host OS and translates the instructions from an application to the underlying OS. It does not need one extra layer of virtual OS, just the binaries and libraries of application bundled in the image. Right? Now, hold on to that thought. We all have been working with docker and an extension with docker-compose. Why? Because it makes our job easy, We are spared from typing hundreds of ad-hoc commands in terminal to set up a slightly or very complicated application with certain dependencies. We can just describe it in a `docker-compose.yml` file and our job is done. However, the problem arises when we have to share that compose file: <br><\/p>\n\n\n\n<ul><li>\nOther users might need to use the file in a different environment, so they will need to edit all the values pertaining to their need, manually, and keep separate compose files for each environment.\n<\/li><li>\nTroubleshooting various configuration issues can be a tedious task since there is no single place where the configuration of the application can be stored. Changes will have to be made in the file.\n<\/li><li>\nThis also makes communication between Dev and Ops team more tricky than it has to be resulting in communication gap and time wastage.\n<\/li><\/ul>\n\n\n\n<p> To have a more clear picture of the issue, we can have look at the below image:<\/p>\n\n\n\n<figure class=\"wp-block-image is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/opstree.com\/blog\/\/wp-content\/uploads\/2019\/08\/31073-without_dockerapp.png\" alt=\"\" width=\"448\" height=\"188\" \/><figcaption>We  have compose file and configuration for separate environments, we make  changes according to environment needs in different compose files, which  could be a long manual task depending on the size of our project.<\/figcaption><\/figure>\n\n\n\n<p><br>All of this points to the fact that there is no way to bundle the applications that use efficiently-bundled docker images. See the irony here? Well, there \u201cwas\u201d no way, until there was. Enter \u2018docker-app\u2019. This, relatively, new tool is the answer to packaging docker-compose applications. I came across it when I was, myself, struggling to re-use a docker-compose application I had written in another environment. As soon as I read about it, I had to try it, which I did and loved. It made the task much easier as it provided a template of compose file and a key-value store for environment dependent parameters. <\/p>\n\n\n\n<p><br><\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/opstree.com\/blog\/\/wp-content\/uploads\/2019\/08\/blog.png\" alt=\"\" class=\"wp-image-1184\" width=\"519\" height=\"238\" \/><figcaption><em>Now, we have an artefact with extention of  &#8216;.dockerapp&#8217;. We can pass configuration values either through CLI or files or both and it will render compose file according to those values<\/em>. <\/figcaption><\/figure>\n\n\n\n<p>Let us now go through an example of how the docker app works. I am going to deploy a dummy application <strong>Spring3hibernate<\/strong> from Opstree Github repository in QA env and later in PROD by making simple configuration changes.\n\n\n\n\n<br>\n\nInstalling docker-app is easy, though, there is one thing one should keep in mind: it can be installed as a plugin in docker-CLI or as standalone CLI tool itself. I will be installing it as a standalone CLI tool on linux. If you wish to install it as a plugin to docker-CLI and\/or on another OS, visit their Github page: <a href=\"https:\/\/github.com\/docker\/app\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/docker\/app<\/a> (Also, please visit github page for basics)\n\n\n<br>\n\n\nBefore continuing, please ensure you have docker-CLI and docker-compose installed.\n\n\n<br>\n\nPlease follow below steps to install docker-app:\n\n\n\n<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ export OSTYPE=\"$(uname | tr A-Z a-z)\"\n$ curl -fsSL --output \"\/tmp\/docker-app-${OSTYPE}.tar.gz\" \\\n\"https:\/\/github.com\/docker\/app\/releases\/download\/v0.8.0\/docker-app-${OSTYPE}.tar.gz\"\n$ tar xf \"\/tmp\/docker-app-${OSTYPE}.tar.gz\" -C \/tmp\/\n$ install -b \"\/tmp\/docker-app-standalone-${OSTYPE}\" \/usr\/local\/bin\/docker-app<\/pre>\n\n\n\n<p>\nCreate a new directory in your home, we\u2019ll call it <em>app home<\/em>:\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cd ~\n$ mkdir spring3hibernate-app\n$ cd spring3hibernate-app\/<\/pre>\n\n\n\n<p>\n\nNow, clone the app from Opstree Github repository. This app needs only mysql as a dependency.\n\n\n\n\n<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ git clone https:\/\/github.com\/opstree\/spring3hibernate.git<\/pre>\n\n\n\n<p>\nWe need to update database properties file and nginx config file with below contents respectively:<br><br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ vim ~\/spring3hibernate-app\/spring3hibernate\/src\/main\/resources\/database.properties<\/pre>\n\n\n\n<p>\n\nReplace below content over there:\n\n\n\n<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">database.driver=com.mysql.jdbc.Driver\ndatabase.url=jdbc:mysql:\/\/mysql:3306\/employeedb\ndatabase.user=admin\ndatabase.password=password\nhibernate.dialect=org.hibernate.dialect.MySQLDialect\nhibernate.show_sql=true\nhibernate.hbm2ddl.auto=update\nupload.dir=c:\/uploads<\/pre>\n\n\n\n<p>\n\nFor nginx conf file:<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ vim ~\/spring3hibernate-app\/spring3hibernate\/nginx\/default.conf\n<\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\">server {\n&nbsp;&nbsp;&nbsp;&nbsp;listen &nbsp; &nbsp; &nbsp; 80;\n&nbsp;&nbsp;&nbsp;&nbsp;server_name&nbsp; localhost;\n\n&nbsp;&nbsp;&nbsp;&nbsp;location \/ {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stub_status on;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proxy_pass http:\/\/springapp1:8080\/;\n\n&nbsp;&nbsp;&nbsp;&nbsp;}\n# redirect server error pages to the static page \/50x.html\n&nbsp;&nbsp;&nbsp;&nbsp;error_page &nbsp; 500 502 503 504&nbsp; \/50x.html;\n&nbsp;&nbsp;&nbsp;&nbsp;location = \/50x.html {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;root &nbsp; \/usr\/share\/nginx\/html;\n&nbsp;&nbsp;&nbsp;&nbsp;}\n\n}<\/pre>\n\n\n\n<p>\n\n\n\n\n\n\nMove \n&#8216;default.conf&#8217; to ~\/spring3hibernate-app\/spring3hibernate\/nginx\/conf\/qa\/\n as we have different conf file for PROD which goes to \n~\/spring3hibernate-app\/spring3hibernate\/nginx\/conf\/prod\/<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">upstream s3hbackend {\n&nbsp;&nbsp;&nbsp;&nbsp;server springapp1:8080;\n&nbsp;&nbsp;&nbsp;&nbsp;server springapp2:8080;\n}\nserver {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;listen 80;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;location \/ {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stub_status on;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proxy_pass http:\/\/s3hbackend;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}\n&nbsp;&nbsp;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# redirect server error pages to the static page \/50x.html\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error_page &nbsp; 500 502 503 504&nbsp; \/50x.html;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;location = \/50x.html {\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;root &nbsp; \/usr\/share\/nginx\/html;\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}\n\n}<\/pre>\n\n\n\n<p>\n\n\nThis is the configuration for the nginx load balancer. Remember this, we\u2019ll use it later.\nLet\u2019s create our docker-app now, make sure you are in the app home directory <br>\nwhen executing this command:\n<br><br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app init --single-file s3h<\/pre>\n\n\n\n<p>\n\n\n\n\nThis will create a single file named s3h.dockerapp which will look like this:&nbsp;\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># This section contains your application metadata.\n# Version of the application\nversion: 0.1.0\n# Name of the application\nname: s3h\n# A short description of the application\ndescription:\n# List of application maintainers with name and email for each\nmaintainers:\n&nbsp;&nbsp;- name: ubuntu\n&nbsp;&nbsp;&nbsp;&nbsp;email:\n\u2028\n---\n# This section contains the Compose file that describes your application services.\nversion: \"3.6\"\nservices: {}\n\u2028\n---\n# This section contains the default values for your application parameters.\n\n{}<\/pre>\n\n\n\n<p>\n\n\nAs you can see this file is divided into three parts, metadata, compose, and parameters. They are all in one file because we used &#8211;single-file switch. We can divide them up in multiple files by using docker-app split command in app home directory, docker-app merge will put them back in one file.\n\nNow, for QA, we have the following configuration for s3h.dockerapp file:<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">version: 0.1.0\nname: s3h\ndescription:\nmaintainers:\n&nbsp;&nbsp;- name: atbk5\n&nbsp;&nbsp;&nbsp;&nbsp;email: adeel.ahmad@opstree.com\n\u2028\n---\nversion: \"3.7\"\nservices:\n&nbsp;&nbsp;mysql:\n&nbsp;&nbsp;&nbsp;&nbsp;image: mysql:5.7\n&nbsp;&nbsp;&nbsp;&nbsp;container_name: mysql\n&nbsp;&nbsp;&nbsp;&nbsp;environment:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MYSQL_ROOT_PASSWORD: ${mysql.env.rootpass}\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MYSQL_DATABASE: ${mysql.env.database}\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MYSQL_USER: ${mysql.env.user}\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MYSQL_PASSWORD: ${mysql.env.userpass}\n&nbsp;&nbsp;&nbsp;&nbsp;restart: always\n&nbsp;&nbsp;&nbsp;&nbsp;networks:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- backend\n&nbsp;&nbsp;&nbsp;&nbsp;volumes:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- db_data:\/var\/lib\/mysql\n\u2028\n&nbsp;&nbsp;spring1:\n&nbsp;&nbsp;&nbsp;&nbsp;depends_on:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- mysql\n&nbsp;&nbsp;&nbsp;&nbsp;build:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;context: .\/spring3hibernate\/\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dockerfile: Dockerfile\n&nbsp;&nbsp;&nbsp;&nbsp;container_name: springapp1\n&nbsp;&nbsp;&nbsp;&nbsp;restart: always\n&nbsp;&nbsp;&nbsp;&nbsp;networks:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- backend\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- frontend\n\u2028\n&nbsp;&nbsp;spring2:\n&nbsp;&nbsp;&nbsp;&nbsp;depends_on:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- mysql\n&nbsp;&nbsp;&nbsp;&nbsp;build:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;context: .\/spring3hibernate\/\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dockerfile: Dockerfile\n&nbsp;&nbsp;&nbsp;&nbsp;container_name: springapp2\n&nbsp;&nbsp;&nbsp;&nbsp;restart: always\n&nbsp;&nbsp;&nbsp;&nbsp;networks:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- backend\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- frontend\n&nbsp;&nbsp;&nbsp;&nbsp;x-enabled: ${spring.app2}\n\u2028\n&nbsp;&nbsp;nginx:\n&nbsp;&nbsp;&nbsp;&nbsp;depends_on:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- spring1\n&nbsp;&nbsp;&nbsp;&nbsp;image: nginx:alpine\n&nbsp;&nbsp;&nbsp;&nbsp;container_name: proxy\n&nbsp;&nbsp;&nbsp;&nbsp;restart: always\n&nbsp;&nbsp;&nbsp;&nbsp;networks:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- frontend\n&nbsp;&nbsp;&nbsp;&nbsp;volumes:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- ${nginx.conf}:\/etc\/nginx\/conf.d\n&nbsp;&nbsp;&nbsp;&nbsp;ports:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- ${nginx.port}:80\n&nbsp;&nbsp;&nbsp;&nbsp;x-enabled: ${nginx.status}\n\u2028\nnetworks:\n&nbsp;&nbsp;frontend:\n&nbsp;&nbsp;backend:\n\u2028\nvolumes:\n&nbsp;&nbsp;db_data:\n\u2028\n---\nmysql:\n&nbsp;&nbsp;env:\n&nbsp;&nbsp;&nbsp;&nbsp;rootpass: password\n&nbsp;&nbsp;&nbsp;&nbsp;database: employeedb\n&nbsp;&nbsp;&nbsp;&nbsp;user: admin\n&nbsp;&nbsp;&nbsp;&nbsp;userpass: password\nnginx:\n&nbsp;&nbsp;conf: \/home\/ubuntu\/dockerApp\/spring3hibernate\/nginx\/conf\/qa\n&nbsp;&nbsp;port: 81\n&nbsp;&nbsp;status: true\nspring:\n  app2: false<\/pre>\n\n\n\n<p>\n\n\n\nAs mentioned before, first part contains app metadata, second part contains actual compose file with lots of variables, and last part contains values of those variables. Special mention here is <em>x-enabled<\/em> variable, docker-app provides functionality to temporarily disable a service using this variable.\n\n\nNow, try a few commands:\n\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app inspect<\/pre>\n\n\n\n<p>\n\n\nIt will produce summary of whole app.\n\n\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app render<\/pre>\n\n\n\n<p>\n\nIt will replace all variables with their values and will produce a compose file\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app render --set nginx.status=\u201dfalse\u201d<\/pre>\n\n\n\n<p>\n\n\nIt will remove nginx from docker-app compose as well as deploy\n\n\n\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app render | docker-compose -f - up<\/pre>\n\n\n\n<p>\n\n\n\nIt\n will spin up all the containers according to rendered compose file. We \ncan see the application running on port 81 of our machine.\n\n\n\n\n\n<br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app --help<\/pre>\n\n\n\n<p>\n\n\nTo check out more commands and play around a bit.\n<br>\n\nAt this point, it will be better to create two directories in <em>app home<\/em>: qa and prod. Create a file in qa: qa-params.yml. Another file in prod: prod-params.yml. Copy all parameters from above s3h.dockerapp file to qa-params.yaml (or not). More importantly, copy below changes in parameters to prod-params.yml\n\n\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">mysql:\n&nbsp;&nbsp;env:\n&nbsp;&nbsp;&nbsp;&nbsp;rootpass: password\n&nbsp;&nbsp;&nbsp;&nbsp;database: employeedb\n&nbsp;&nbsp;&nbsp;&nbsp;user: admin\n&nbsp;&nbsp;&nbsp;&nbsp;userpass: password\nnginx:\n&nbsp;&nbsp;conf: \/home\/ubuntu\/dockerApp\/spring3hibernate\/nginx\/conf\/prod\n&nbsp;&nbsp;port: 80\n&nbsp;&nbsp;status: true\nspring:\n  app2: true<\/pre>\n\n\n\n<p>\n\nWe are going to loadbalance <em>springapp1<\/em> and <em>springapp2<\/em> in PROD environment, since we have enabled <em>springapp2<\/em> using x-enabled parameter. We have also changed nginx conf bind path to the new conf file and host port for nginx to 80 (for Production). All so easily. Run command: \n<br><br><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app render --parameters-file .\/prod\/prod-params.yaml<\/pre>\n\n\n\n<p>\n\n\n\n\n\nThis command will produce a compose file ready for production deployment. Now run:\n\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app render --parameters-file .\/prod\/prod-params.yml | docker-compose -f - up<\/pre>\n\n\n\n<p>\n\n\n\n\nAnd production is deployed \u2026 Visit port 80 of your localhost to verify.\n\nWhat&#8217;s more exciting is that we can also share our docker-apps through docker hub, we can tag the app and push it to our remote registry as images after logging in:\n\n\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker login<\/pre>\n\n\n\n<p>\n\n\n\n\nProvide your username and password for docker hub, create an account if not yet created.\n<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker-app push --tag atbk5\/s3h.dockerapp:latest<\/pre>\n\n\n\n<p> If we wish to upload additional files as well, we will have to split our project using docker-app split and put additional files in the directory before pushing. The additional files will go as attachments which can be accessed later. <br><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">\n<strong>Conclusion<\/strong>\n<\/h4>\n\n\n\n<p> With the arrival of docker app, our large, composite, and containerized applications can also be shipped and re-used as images. That is cool. But there\u2019s something cooler which we haven\u2019t explored yet. Deploying our docker-apps on kubernetes with the goal of exploring how far in management, and how optimal in delivery, we can go with our applications. Let\u2019s keep this as a topic for the next blog. Until then, have a nice one. \ud83d\ude42<\/p>\n\n\n\n<p>Image Source: <a href=\"https:\/\/reflectoring.io\/externalize-configuration\/\" target=\"_blank\" rel=\"noopener\">https:\/\/reflectoring.io\/externalize-configuration\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>When docker was released as a new containerization tool, it took the market by a storm. With its lightweight images, multi-os support, and ability to ship containers, it\u2019s popularity only roared. I have been using it for more than six months now, I can see why it is so. Hypervisors, another type of virtualizing tools,\u00a0 &hellip; <a href=\"https:\/\/opstree.com\/blog\/2019\/08\/06\/docker-compose-as-a-bundled-application\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Docker-Compose As A Bundled Application&#8221;<\/span><\/a><\/p>\n","protected":false},"author":155574231,"featured_media":29900,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[28070474,1],"tags":[27998418,182564,768739305,685617500,308799219,768739301,91482705,41395],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/opstree.com\/blog\/wp-content\/uploads\/2025\/11\/DevSecOps-1.jpg","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pfDBOm-iy","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/1150"}],"collection":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/users\/155574231"}],"replies":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/comments?post=1150"}],"version-history":[{"count":12,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/1150\/revisions"}],"predecessor-version":[{"id":1371,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/1150\/revisions\/1371"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/media\/29900"}],"wp:attachment":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/media?parent=1150"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/categories?post=1150"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/tags?post=1150"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}