{"id":2306,"date":"2020-03-09T01:59:31","date_gmt":"2020-03-08T22:59:31","guid":{"rendered":"http:\/\/kusuaks7\/?p=1911"},"modified":"2024-01-02T06:53:36","modified_gmt":"2024-01-02T06:53:36","slug":"docker-best-practices-for-data-scientists","status":"publish","type":"post","link":"https:\/\/www.experfy.com\/blog\/bigdata-cloud\/docker-best-practices-for-data-scientists\/","title":{"rendered":"Docker Best Practices for Data Scientists"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"2306\" class=\"elementor elementor-2306\" data-elementor-post-type=\"post\">\n\t\t\t\t\t\t<section class=\"has_eae_slider elementor-section elementor-top-section elementor-element elementor-element-1c797f3b elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"1c797f3b\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"has_eae_slider elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-3cd0b859\" data-id=\"3cd0b859\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-9c74ec8 elementor-widget elementor-widget-text-editor\" data-id=\"9c74ec8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Docker \u2026 whale \u2026 you get it.<\/p>\n\n<article><section>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d197838 elementor-widget elementor-widget-text-editor\" data-id=\"d197838\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"bd13\" data-selectable-paragraph=\"\">As a data scientist, I grapple with Docker on a daily basis. Creating images, spinning up contains have become as common as writing Python scripts for me. And this journey has its achievements as well as moments, \u201cI wish I knew that before\u201d.<\/p>\n<p id=\"6db6\" data-selectable-paragraph=\"\">This article discusses some of the best practices while using Docker for your data science projects. By no means this is an exhaustive checklist. But this covers most things I\u2019ve come across as a data scientist.<\/p>\n<p id=\"a98b\" data-selectable-paragraph=\"\">This article assumes basic-to-moderate knowledge of Docker. For example, you should know what Docker is used for and should be able to comfortably write a Dockerfile and understand Docker commands like\u00a0<code>RUN<\/code>\u00a0,\u00a0<code>CMD<\/code>, etc. If not, have a read-through this\u00a0<a href=\"https:\/\/docs.docker.com\/develop\/develop-images\/dockerfile_best-practices\/\" target=\"_blank\" rel=\"noopener nofollow noreferrer\">article\u00a0<\/a>from official Docker site. You can also explore through the collection of articles found there.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c5a2aaf elementor-widget elementor-widget-heading\" data-id=\"c5a2aaf\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><h1 id=\"ab3b\" data-selectable-paragraph=\"\">Why Docker?<\/h1><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0c35443 elementor-widget elementor-widget-text-editor\" data-id=\"0c35443\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"e309\" data-selectable-paragraph=\"\">Since Docker has been released it has taken the world by a storm. Before the era of Docker, virtual machines used to fill that void. But Docker offers so much than virtual machines.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7f8faf6 elementor-widget elementor-widget-heading\" data-id=\"7f8faf6\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><h1 id=\"f5bd\" data-selectable-paragraph=\"\">Advantages of docker<\/h1><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1ac4c2d elementor-widget elementor-widget-text-editor\" data-id=\"1ac4c2d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<ul>\n \t<li id=\"67cf\" data-selectable-paragraph=\"\">Isolation \u2014 isolated environment regardless of the changes in the underlying OS\/infrastructure, installed software, updates<\/li>\n \t<li id=\"f087\" data-selectable-paragraph=\"\">light-weight \u2014 shares the OS kernel avoiding having OS kernel for each container<\/li>\n \t<li id=\"0f74\" data-selectable-paragraph=\"\">performance \u2014 being lightweight allows many containers to be run simultaneously on the same OS<\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ee2a351 elementor-widget elementor-widget-heading\" data-id=\"ee2a351\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><h1 id=\"eeb7\" data-selectable-paragraph=\"\">Primer on Docker<\/h1><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5c07535 elementor-widget elementor-widget-text-editor\" data-id=\"5c07535\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"ee58\" data-selectable-paragraph=\"\">Docker has three important concepts.<\/p>\n<p id=\"36be\" data-selectable-paragraph=\"\"><strong>Images\u00a0<\/strong>\u2014 This is a set of runnable libraries and binaries that represents a development\/production\/testing environment. You can download\/create an image in the following ways.<\/p>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9c8cf05 elementor-widget elementor-widget-text-editor\" data-id=\"9c8cf05\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\n<ul>\n \t<li id=\"c6a7\" data-selectable-paragraph=\"\">Pulling from an image registry: e.g.\u00a0<code>docker pull alpine<\/code>\u00a0. What happens here is that Docker will look locally in your computer for an image named\u00a0<code>alpine<\/code>\u00a0, if it\u2019s not found, it looks in\u00a0<a href=\"https:\/\/hub.docker.com\/\" target=\"_blank\" rel=\"noopener nofollow noreferrer\">Dockerhub<\/a><\/li>\n \t<li id=\"b5b9\" data-selectable-paragraph=\"\">Building an image locally using a Dockerfile: e.g.\u00a0<code>docker build . -t &lt;image_name&gt;:&lt;image_version&gt;<\/code>\u00a0. Here you\u2019re not trying to download\/pull images, rather, you are building your own image. But this is not entirely true, as a\u00a0<code>Dockerfile<\/code>\u00a0contains a line that starts with\u00a0<code>FROM &lt;base-image&gt;<\/code>\u00a0which looks for a base image to start with, which might be pulled from Dockerhub.<\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5955573 elementor-widget elementor-widget-text-editor\" data-id=\"5955573\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"7400\" data-selectable-paragraph=\"\"><strong>Containers<\/strong>&#8211; This is a running instance of an image. You can stand up a container using the syntax\u00a0<code>`docker container run &lt;arguments&gt; &lt;image&gt; &lt;command&gt;\u00a0<\/code>, for example to create a container from the\u00a0<code>alpine<\/code>\u00a0image use,\u00a0<code>docker container run -it alpine \/bin\/bash<\/code>\u00a0command.<\/p>\n<p id=\"ae37\" data-selectable-paragraph=\"\"><strong>Volumes\u00a0<\/strong>\u2014 Volumes are used to permanently\/temporarily store data (e.g. logs, downloaded data) for containers to use. Additionally, volumes can be shared among multiple containers. You can use volumes in couple of ways.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-56f2112 elementor-widget elementor-widget-text-editor\" data-id=\"56f2112\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<ul>\n \t<li id=\"9d01\" data-selectable-paragraph=\"\">Creating a volume: You can create a volume using\u00a0<code>docker volume create &lt;volume_name&gt;<\/code>\u00a0command.\u00a0<em>Note that, information\/changes stored here will be lost if that volume is deleted.<\/em><\/li>\n \t<li id=\"3278\" data-selectable-paragraph=\"\">Bind mount a volume: You can also bind mount an existing volume from the host to your container using\u00a0<code>-v &lt;source&gt;:&lt;target&gt;<\/code>\u00a0syntax. For example, if you need to mount the\u00a0<code>\/my_data<\/code>\u00a0volume to the container as the\u00a0<code>\/data<\/code>\u00a0volume, you can do,\u00a0<code>docker container run -it -v \/my_data:\/data alpine \/bin\/bash<\/code>\u00a0command.\u00a0<em>The changes you do at the mount point will be reflected on the host.<\/em><\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fd1c77e elementor-widget elementor-widget-heading\" data-id=\"fd1c77e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><h1 id=\"670e\" data-selectable-paragraph=\"\">1. Creating images<\/h1><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2079bb4 elementor-widget elementor-widget-heading\" data-id=\"2079bb4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"0f37\" data-selectable-paragraph=\"\">1. Keep the image small, avoid caching<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7da25da elementor-widget elementor-widget-text-editor\" data-id=\"7da25da\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"2857\" data-selectable-paragraph=\"\">Two common things you\u2019d have to do when building images is,<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-07fa82b elementor-widget elementor-widget-text-editor\" data-id=\"07fa82b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<ul>\n \t<li id=\"9290\" data-selectable-paragraph=\"\">Install Linux packages<\/li>\n \t<li id=\"159c\" data-selectable-paragraph=\"\">Install Python libraries<\/li>\n<\/ul>\n<p id=\"f6c8\" data-selectable-paragraph=\"\">When installing these packages and libraries the package mangers will cached data so local data will be used if you want to install them again. But this increases the image size unnecessarily. And docker images are supposed to be light-weight as possible.<\/p>\n<p id=\"4e11\" data-selectable-paragraph=\"\">When installing Linux packages remember to remove any cached data by adding the last line to your\u00a0<code>apt-get install<\/code>\u00a0command.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-588c6cf elementor-widget elementor-widget-text-editor\" data-id=\"588c6cf\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div style=\"background: #eee; border: 1px solid #ccc; padding: 5px 10px;\"><span style=\"font-family: courier new,courier,monospace;\">RUN apt-get update &amp;&amp; apt-get install tini &amp;&amp;\nrm -rf \/var\/lib\/apt\/lists\/*<\/span><\/div>\n<p id=\"ee3c\" data-selectable-paragraph=\"\">When installing Python packages, to avoid caching, do the following.<\/p>\n<pre>RUN pip3 install &lt;library-1&gt; &lt;library-2&gt; --no-cache-dir`<\/pre>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-bb6d39c elementor-widget elementor-widget-heading\" data-id=\"bb6d39c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"174d\" data-selectable-paragraph=\"\">2. Separate out Python libraries to a requirements.txt<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7512872 elementor-widget elementor-widget-text-editor\" data-id=\"7512872\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"eea1\" data-selectable-paragraph=\"\">The last command you saw brings us to the next point. It is better to separate Python libraries to a\u00a0<code>requirements.txt<\/code>\u00a0file and install libraries using that file using the following syntax.<\/p>\n<p id=\"74d7\" data-selectable-paragraph=\"\"><code>RUN pip3 install -r requirements.txt --no-cache-dir<\/code><\/p>\n<p id=\"2e4e\" data-selectable-paragraph=\"\">This gives a nice separation of Dockerfile doing \u201cDocker stuff\u201d and not (explicitly) worrying about \u201cPython stuff\u201d. Additionally, if you have multiple Dockerfiles (e.g. for production \/ development \/ testing) and they all want the same libraries installed, you can reuse this command easily. The\u00a0<code>requirements.txt<\/code>\u00a0file is just a bunch of library names.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4554e31 elementor-widget elementor-widget-text-editor\" data-id=\"4554e31\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div style=\"background: #eee; border: 1px solid #ccc; padding: 5px 10px;\"><span style=\"font-family: courier new,courier,monospace;\">numpy==1.18.0\nscikit-learn==0.20.2\npandas==0.25.0<\/span><\/div>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b4db3b0 elementor-widget elementor-widget-heading\" data-id=\"b4db3b0\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"01dd\" data-selectable-paragraph=\"\">3. Fixing library versions<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6277c72 elementor-widget elementor-widget-text-editor\" data-id=\"6277c72\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"436f\" data-selectable-paragraph=\"\">Note how in the\u00a0<code>requirements.txt<\/code>\u00a0I am freezing the version I want to install. This is very important. Because otherwise, every time you build your Docker image, you might be installing different versions of different things. \u201c<a href=\"https:\/\/en.wikipedia.org\/wiki\/Dependency_hell\" target=\"_blank\" rel=\"noopener nofollow noreferrer\">Dependency Hell<\/a>\u201d is real.<\/p>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7e10975 elementor-widget elementor-widget-heading\" data-id=\"7e10975\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><h1 id=\"5d0c\" data-selectable-paragraph=\"\">Running containers<\/h1><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e1d6bdf elementor-widget elementor-widget-heading\" data-id=\"e1d6bdf\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"5628\" data-selectable-paragraph=\"\">1. Embrace the non-root user<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1b160d0 elementor-widget elementor-widget-text-editor\" data-id=\"1b160d0\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"8874\" data-selectable-paragraph=\"\">When you run the containers, if you don\u2019t specify an user to run as, it is going to assume\u00a0<code>root<\/code>\u00a0user. I\u2019m not going to lie. my naive self used to love having the ability to use\u00a0<code>sudo\u00a0<\/code>or being\u00a0<code>root<\/code>\u00a0to get things my way (especially to get around permission). But if I\u2019ve learnt one thing, it\u2019s that having unnecessary privileges than needed is an exacerbation catalyst, leading to even more problems.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-fbf44ce elementor-widget elementor-widget-text-editor\" data-id=\"fbf44ce\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"a1d1\" data-selectable-paragraph=\"\">To run a container as a non-root user, simply do<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-9024298 elementor-widget elementor-widget-text-editor\" data-id=\"9024298\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<ul>\n \t<li id=\"94d4\" data-selectable-paragraph=\"\"><code>docker run -it -u &lt;user-id&gt;:&lt;group-id&gt; &lt;image-name&gt; &lt;command&gt;<\/code><\/li>\n<\/ul>\n<p id=\"2bdc\" data-selectable-paragraph=\"\">Or, if you want to jump into an existing container do,<\/p>\n\n<ul>\n \t<li id=\"926a\" data-selectable-paragraph=\"\"><code>docker exec -it -u &lt;user-id&gt;:&lt;group-id&gt; &lt;container-id&gt; &lt;command&gt;<\/code><\/li>\n<\/ul>\n<p id=\"479b\" data-selectable-paragraph=\"\">For example, you can match the user id and group id of the host by assigning\u00a0<code>&lt;user-id&gt;\u00a0<\/code>as\u00a0<code>$(id -u)<\/code>\u00a0and\u00a0<code>&lt;group-id&gt;<\/code>\u00a0as\u00a0<code>$(id -g)<\/code>\u00a0.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5090d47 elementor-widget elementor-widget-text-editor\" data-id=\"5090d47\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<blockquote>\n<p id=\"d8ea\" data-selectable-paragraph=\"\">Beware of how different operating systems assign user IDs and group IDs. For example your user ID\/group ID on a MacOS might be a\u00a0<strong>pre-assigned\/reserved<\/strong>\u00a0user ID \/ group ID inside an Ubuntu container.<\/p>\n<\/blockquote>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7f40f57 elementor-widget elementor-widget-heading\" data-id=\"7f40f57\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"c477\" data-selectable-paragraph=\"\">2. Creating a non-priviledged user<\/h2>\n<\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d914fbe elementor-widget elementor-widget-text-editor\" data-id=\"d914fbe\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"9fea\" data-selectable-paragraph=\"\">It is great that we can log in as a non-root user to our host-away from host. But if you login like this, you\u2019re a user without a username. Because, obviously the container has no-clue where that user id came from. And you need to remember and type these user id and group id everytime you want to spin-up a container or\u00a0<code>exec<\/code>\u00a0into one. So for that, you can include this user\/group creation as a part of the\u00a0<code>Dockerfile<\/code>\u00a0.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f3e6c4a elementor-widget elementor-widget-text-editor\" data-id=\"f3e6c4a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div style=\"background: #eee; border: 1px solid #ccc; padding: 5px 10px;\"><span style=\"font-family: courier new,courier,monospace;\">ARG UID=1000\nARG GID=1000<\/span><\/div>\n&nbsp;\n<ul>\n \t<li id=\"53e4\" data-selectable-paragraph=\"\">First add\u00a0<code>ARG UID=1000<\/code>\u00a0and\u00a0<code>ARG GID=1000<\/code>\u00a0to the\u00a0<code>Dockerfile<\/code>\u00a0.\u00a0<code>UID\u00a0<\/code>and\u00a0<code>GID<\/code>\u00a0are environment variables in the container to which you\u2019ll pass the value at\u00a0<code>docker build<\/code>stage (defaults to 1000).<\/li>\n \t<li id=\"a1ee\" data-selectable-paragraph=\"\">Then add a Linux group in the image with the group ID\u00a0<code>GID<\/code>\u00a0using,\u00a0<code>RUN groupadd -g $GID john-group<\/code>\u00a0.<\/li>\n \t<li id=\"fdc1\" data-selectable-paragraph=\"\">Next add a Linux user in the image with some user ID\u00a0<code>UID<\/code>\u00a0using,\u00a0<code>useradd -N -l -u $UID -g john-group -G sudo john<\/code>\u00a0. You can see that here we are adding\u00a0<code>john<\/code>\u00a0to the\u00a0<code>sudo<\/code>\u00a0group. But this is an optional thing. You can leave it out if you are 100% sure you don\u2019t need\u00a0<code>sudo<\/code>\u00a0permission.<\/li>\n<\/ul>\n<p id=\"6e4b\" data-selectable-paragraph=\"\">Then during image build, you can pass values for these arguments like,<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1ae5266 elementor-widget elementor-widget-text-editor\" data-id=\"1ae5266\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<ul>\n \t<li><span style=\"font-family: courier new,courier,monospace;\">docker build &lt;build_dir&gt; -t &lt;image&gt;:&lt;image_tag&gt; &#8211;build-arg UID=&lt;uid-value&gt; &#8211;build-arg GID=&lt;gid-value&gt;<\/span><\/li>\n<\/ul>\n<p data-selectable-paragraph=\"\"><\/p>\n<p id=\"95e5\" data-selectable-paragraph=\"\">For example,<\/p>\n\n<ul>\n \t<li id=\"f3bf\" data-selectable-paragraph=\"\"><code>docker build . -t docker-tut:latest --build-arg UID=$(id -u) --build-arg GID=$(id -g)<\/code><\/li>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8d15b4c elementor-widget elementor-widget-text-editor\" data-id=\"8d15b4c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<\/ul>\n<p id=\"8904\" data-selectable-paragraph=\"\">Having a non-privileged user helps you to run processes that should not have root permission. For example, why run your Python script as root when all it does is reading from a dir (e.g. data) and writing to one (e.g. model). And as an added benefit, if you match the user ID and group ID of the host, within the container, all the files you created will have your host user\u2019s ownership. So if you bind-mount these files (or create new files) they will still look like you created them on the host.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-740b5dc elementor-widget elementor-widget-heading\" data-id=\"740b5dc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><h1 id=\"b10d\" data-selectable-paragraph=\"\">Creating volumes<\/h1><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-41a63f7 elementor-widget elementor-widget-heading\" data-id=\"41a63f7\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"d380\" data-selectable-paragraph=\"\">1. Separate artifacts using volumes<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-dcf18dd elementor-widget elementor-widget-text-editor\" data-id=\"dcf18dd\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"23a6\" data-selectable-paragraph=\"\">As a data scientist, obviously you\u2019ll be working with various artifacts (e.g. data, models and code). You can have the code in one volume (e.g.\u00a0<code>\/app<\/code>\u00a0) and data in another (e.g.\u00a0<code>\/data<\/code>\u00a0). This will provide a nice structure for your Docker image as well as get rid of any host-level artifact dependencies.<\/p>\n<p id=\"6642\" data-selectable-paragraph=\"\">What did I mean by artifact dependencies? Say you have the code at\u00a0<code>\/home\/&lt;user&gt;\/code\/src<\/code>\u00a0and the data at\u00a0<code>\/home\/&lt;user&gt;\/code\/data<\/code>\u00a0. If you copy\/mount\u00a0<code>\/home\/&lt;user&gt;\/code\/src<\/code>\u00a0to the volume<code>\/app<\/code>\u00a0and\u00a0<code>\/home\/&lt;user&gt;\/code\/data<\/code>\u00a0to the volume\u00a0<code>\/data<\/code>\u00a0. It doesn\u2019t matter if the location of the code and data changes on the host. They will always be available at the same location inside the Docker container as long as you mount those artifacts. So you could fix those paths nicely in your Python script as follows.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-19a55d0 elementor-widget elementor-widget-text-editor\" data-id=\"19a55d0\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div style=\"background: #eee; border: 1px solid #ccc; padding: 5px 10px;\"><span style=\"font-family: courier new,courier,monospace;\">data_dir = &#8220;\/data&#8221;\nmodel_dir = &#8220;\/models&#8221;\nsrc_dir = &#8220;\/app&#8221;<\/span><\/div>\n<p data-selectable-paragraph=\"\"><\/p>\n<p id=\"243e\" data-selectable-paragraph=\"\">You can\u00a0<code>COPY<\/code>\u00a0the necessary code and data into the image using<\/p>\n\n<div style=\"background: #eee; border: 1px solid #ccc; padding: 5px 10px;\"><span style=\"font-family: courier new,courier,monospace;\">COPY test-data \/data\nCOPY test-code \/app<\/span><\/div>\n<p data-selectable-paragraph=\"\"><\/p>\n<p id=\"066e\" data-selectable-paragraph=\"\">Note that\u00a0<code>test-data<\/code>\u00a0and\u00a0<code>test-code<\/code>\u00a0are directories on the host.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-89e6d09 elementor-widget elementor-widget-heading\" data-id=\"89e6d09\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"e39b\" data-selectable-paragraph=\"\">2. Bind-mount directories during development<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-df74789 elementor-widget elementor-widget-text-editor\" data-id=\"df74789\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"6d0f\" data-selectable-paragraph=\"\">Great thing about bind-mounting is that, whatever you do in the container is reflected on the host itself. This is great when you\u2019re doing developments and you want to debug your project. Let\u2019s see this through an example.<\/p>\n<p id=\"3b71\" data-selectable-paragraph=\"\">Say you created your docker image by running:<\/p>\n<p id=\"9b96\" data-selectable-paragraph=\"\"><code>docker build &lt;build-dir&gt; &lt;image-name&gt;:&lt;image-version&gt;<\/code><\/p>\n<p id=\"2378\" data-selectable-paragraph=\"\">Now you can stand up a container from this image using:<\/p>\n<p id=\"f925\" data-selectable-paragraph=\"\"><code>docker run -it &lt;image-name&gt;:&lt;image-version&gt; -v \/home\/&lt;user&gt;\/my_code:\/code<\/code><\/p>\n<p id=\"23e1\" data-selectable-paragraph=\"\">Now you can run the code within the container and debug at the same time and the changes to the code will be reflected on the host. And this loops back to the benefit of using the same host user ID and group ID in your container. All changes you do, looks like came from the user on the host.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-a0e28de elementor-widget elementor-widget-heading\" data-id=\"a0e28de\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"37b1\" data-selectable-paragraph=\"\">3. NEVER bind-mount critical directories of the host<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1fe14cc elementor-widget elementor-widget-text-editor\" data-id=\"1fe14cc\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"4286\" data-selectable-paragraph=\"\">Funny story! I once mounted the home directory of my machine to a Docker container and managed to change the permission of the home directory. No need to say that I was unable to log into the system afterwards and spent a good couple of hours fixing this. Therefore, mount only what is needed.<\/p>\n<p id=\"3844\" data-selectable-paragraph=\"\">For example, say you have three directories that you want to mount during developments:<\/p>\n\n<ul>\n \t<li id=\"b14c\" data-selectable-paragraph=\"\"><code>\/home\/&lt;user&gt;\/my_data<\/code><\/li>\n \t<li id=\"e2b4\" data-selectable-paragraph=\"\"><code>\/home\/&lt;user&gt;\/my_code<\/code><\/li>\n \t<li id=\"28c7\" data-selectable-paragraph=\"\"><code>\/home\/&lt;user&gt;\/my_model<\/code><\/li>\n<\/ul>\n<p id=\"1812\" data-selectable-paragraph=\"\">You might be very tempted to mount\u00a0<code>\/home\/&lt;user&gt;<\/code>\u00a0with a single line of code. But it is definitely worth writing three lines to mount these individual sub directories separately, as it will save you several painstaking hours (if not days) of your life.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0336d57 elementor-widget elementor-widget-heading\" data-id=\"0336d57\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">\n<h1 id=\"7705\" data-selectable-paragraph=\"\">Additional tips<\/h1><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-135fb41 elementor-widget elementor-widget-heading\" data-id=\"135fb41\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"98cd\" data-selectable-paragraph=\"\">1. Know the difference between ADD and COPY<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1646e20 elementor-widget elementor-widget-text-editor\" data-id=\"1646e20\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"e880\" data-selectable-paragraph=\"\">You probably know that there are two Docker commands called\u00a0<code>ADD<\/code>\u00a0and\u00a0<code>COPY<\/code>\u00a0. What\u2019s the difference?<\/p>\n\n<ul>\n \t<li id=\"4172\" data-selectable-paragraph=\"\"><code>ADD<\/code>\u00a0can be used to download files from URLs when used like,\u00a0<code>ADD &lt;url&gt;<\/code><\/li>\n \t<li id=\"e5ce\" data-selectable-paragraph=\"\"><code>ADD<\/code>\u00a0when given a compressed file (e.g.\u00a0<code>tar.gz<\/code>\u00a0) will extract the file to the provided location.<\/li>\n \t<li id=\"0b38\" data-selectable-paragraph=\"\"><code>COPY<\/code>\u00a0copies a given file\/folder to the specified location in the container.<\/li>\n<\/ul>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6d98c6d elementor-widget elementor-widget-heading\" data-id=\"6d98c6d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"1ea5\" data-selectable-paragraph=\"\">2. Difference between ENTRYPOINT and CMD<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2c9f12a elementor-widget elementor-widget-text-editor\" data-id=\"2c9f12a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"1b0f\" data-selectable-paragraph=\"\">A great analogy that comes to my mind is, think of\u00a0<code>ENTRYPOINT<\/code>\u00a0as a vehicle and\u00a0<code>CMD<\/code>\u00a0as the controls in that vehicle (e.g. accelerator, brakes, steering wheel).\u00a0<code>ENTRYPOINT<\/code>\u00a0it self does nothing, it\u2019s just a vessel for what you want to do within that container. It just stays stand-by for any incoming commands you push to the container.<\/p>\n<p id=\"1c4c\" data-selectable-paragraph=\"\">A command\u00a0<code>CMD<\/code>\u00a0is what actually gets executed within a container. For example\u00a0<code>bash<\/code>\u00a0would create a shell in your container so you could work withing the container like you work on a normal terminal on Ubuntu.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1db1b2b elementor-widget elementor-widget-heading\" data-id=\"1db1b2b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\"><h2 id=\"511f\" data-selectable-paragraph=\"\">3. Copying files to existing containers<\/h2><\/h2>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4643e2e elementor-widget elementor-widget-text-editor\" data-id=\"4643e2e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"10f6\" data-selectable-paragraph=\"\">Not again! I\u2019ve created this container and forgot to add this file to the image. It takes so long to build the image. Is there any way I could cheat and add this to the existing container?<\/p>\n<p id=\"3645\" data-selectable-paragraph=\"\">Yes there is, you could use\u00a0<code>docker cp<\/code>\u00a0command for this. Simply do,<\/p>\n<p id=\"ff52\" data-selectable-paragraph=\"\"><code>docker cp &lt;src&gt; &lt;container&gt;:&lt;dest&gt;<\/code><\/p>\n<p id=\"a7ce\" data-selectable-paragraph=\"\">Next time you jump into the container you will see the copied file at\u00a0<code>&lt;dest&gt;<\/code>\u00a0. But remember to actually change the\u00a0<code>Dockerfile<\/code>\u00a0to copy the necessary files at build time.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5a0a320 elementor-widget elementor-widget-heading\" data-id=\"5a0a320\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">\n<h1 id=\"9495\" data-selectable-paragraph=\"\">3. Conclusion<\/h1><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-51da7a9 elementor-widget elementor-widget-text-editor\" data-id=\"51da7a9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p id=\"da6a\" data-selectable-paragraph=\"\">Great! That\u2019s all folks. We discussed,<\/p>\n\n<ul>\n \t<li id=\"c360\" data-selectable-paragraph=\"\">What Docker images \/ containers \/ volumes are?<\/li>\n \t<li id=\"bb34\" data-selectable-paragraph=\"\">How to write a good Dockerfile<\/li>\n \t<li id=\"21ef\" data-selectable-paragraph=\"\">How to spin-up containers as a non-root user<\/li>\n \t<li id=\"0c5a\" data-selectable-paragraph=\"\">How to do proper volume mounting in Docker<\/li>\n \t<li id=\"923f\" data-selectable-paragraph=\"\">And bonus tips such as using\u00a0<code>docker cp<\/code>\u00a0to save the day<\/li>\n<\/ul>\n<p id=\"7b37\" data-selectable-paragraph=\"\">Now you should have grown your confidence to look at Docker in the eyes and say \u201c<em>You can\u2019t scare me<\/em>\u201d. Jokes aside, it always pays off to know what you\u2019re doing with Docker. Because if you\u2019re not careful you can bring down a whole server and disrupt the work of everyone else whose working on that same machine.<\/p>\n\n<\/section><\/article>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>This article discusses about Docker images, containers, and volumes as also how to write a good Dockerfile, how to spin-up containers as a non-root user, how to do proper volume mounting in Docker and bonus tips to save the day. You will grow your confidence to look at Docker and it always pays off to know what you&rsquo;re doing with Docker. Because if you&rsquo;re not careful you can bring down a whole server and disrupt the work of everyone else whose working on that same machine.<\/p>\n","protected":false},"author":742,"featured_media":8295,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"content-type":"","footnotes":""},"categories":[187],"tags":[94],"ppma_author":[3588],"class_list":["post-2306","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-bigdata-cloud","tag-data-science"],"authors":[{"term_id":3588,"user_id":742,"is_guest":0,"slug":"thushan-ganegedara","display_name":"Thushan Ganegedara","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g","user_url":"","last_name":"Ganegedara","first_name":"Thushan","job_title":"","description":"Thushan Ganegedara is Data Scientist at QBE Insurance. He is also a DataCamp Course Instructor and &nbsp;Authored NLP with Tensorflow (Packt)."}],"_links":{"self":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/2306","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/users\/742"}],"replies":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/comments?post=2306"}],"version-history":[{"count":6,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/2306\/revisions"}],"predecessor-version":[{"id":35290,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/posts\/2306\/revisions\/35290"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/media\/8295"}],"wp:attachment":[{"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/media?parent=2306"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/categories?post=2306"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/tags?post=2306"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.experfy.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=2306"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}