<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>The Ops Community ⚙️: Martti Laine</title>
    <description>The latest articles on The Ops Community ⚙️ by Martti Laine (@codeclown).</description>
    <link>https://community.ops.io/codeclown</link>
    <image>
      <url>https://community.ops.io/images/QUZ6rKC5KgLdBXTG3h4s_EGqU9bOPbXZjl0DEDcyPkE/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL3Vz/ZXIvcHJvZmlsZV9p/bWFnZS8yMjIvYjU5/ZDM5ZTAtNmY2Zi00/ZjQyLTg0MjUtNTgx/ZmRlMzliMTExLnBu/Zw</url>
      <title>The Ops Community ⚙️: Martti Laine</title>
      <link>https://community.ops.io/codeclown</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://community.ops.io/feed/codeclown"/>
    <language>en</language>
    <item>
      <title>Understanding Helm upgrade flags --reset-values and --reuse-values</title>
      <dc:creator>Martti Laine</dc:creator>
      <pubDate>Thu, 26 May 2022 10:24:04 +0000</pubDate>
      <link>https://community.ops.io/codeclown/understanding-helm-upgrade-flags-reset-values-and-reuse-values-51b3</link>
      <guid>https://community.ops.io/codeclown/understanding-helm-upgrade-flags-reset-values-and-reuse-values-51b3</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href="https://shipmight.com"&gt;shipmight.com&lt;/a&gt;. Shipmight is a self-hosted PaaS powered by Kubernetes. Check it out if that sounds interesting!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Link to original post: &lt;a href="https://shipmight.com/articles/understanding-helm-upgrade-reset-reuse-values"&gt;https://shipmight.com/articles/understanding-helm-upgrade-reset-reuse-values&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every now and then you’ll need to use the &lt;code&gt;--reset-values&lt;/code&gt; and &lt;code&gt;--reuse-values&lt;/code&gt; flags when running &lt;code&gt;helm upgrade&lt;/code&gt;. Let’s dive into how they actually work, and also look at a gotcha when the values of a chart have changed in-between upgrades.&lt;/p&gt;

&lt;p&gt;Here are the different scenarios visualized in a table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/wd58GexNZd6mw4Ch8toP587iNk7yLv4ZLSwxgy2TiS0/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnRhYmxlLnBu/Zw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/wd58GexNZd6mw4Ch8toP587iNk7yLv4ZLSwxgy2TiS0/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnRhYmxlLnBu/Zw" alt="Table of helm upgrade scenarios" width="880" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To learn about them in more depth, continue reading…&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Default behaviour&lt;/li&gt;
&lt;li&gt;Explicit behaviour using flags&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--reuse-values&lt;/code&gt; and schema change&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The point of Helm is that you can customize parameters of a chart (in Helm lingo, parameters are called just values).&lt;/p&gt;

&lt;p&gt;For example, as part of the loki-stack chart you can enable and disable components of the stack by setting a value via &lt;code&gt;--set&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;example-loki grafana/loki-stack &lt;span class="nt"&gt;--set&lt;/span&gt; grafana.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same applies for upgrading an existing release. You can change a value and the chart is expected to reconfigure itself according to your customization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm upgrade example-loki grafana/loki-stack &lt;span class="nt"&gt;--set&lt;/span&gt; grafana.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the chart name had to be specified even though you just wanted to update the values using the same chart that was installed already? That is because the upgrade-command is also able to upgrade the release to a different version of the chart (technically also to any other chart, although that does not sound like a common usecase). A common workflow is to update charts from remote repos, and then upgrade releases to the latest version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo update
helm upgrade ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s also possible to set a specific version of a chart to upgrade to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm upgrade example-loki grafana/loki-stack &lt;span class="nt"&gt;--version&lt;/span&gt; 1.2.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fact that you can reconfigure and update charts is the whole purpose of Helm. I’m sure you are familiar with all of the above if you’ve used Helm before.&lt;/p&gt;

&lt;p&gt;But the way that configuration changes are merged together may catch you off guard. The upgrade-command contains some internal logic, which can seem inconsistent. It may get even more confusing when the values of the chart you’re upgrading to have changed since the original chart version. I personally had to actually test out the different variations one-by-one to understand all of it fully.&lt;/p&gt;

&lt;p&gt;The first potential surprise is that the difference in merging behaviour is actually determined by this: are you also setting values during the upgrade?&lt;/p&gt;

&lt;p&gt;If you are, Helm implicitly uses a “reset values” strategy.&lt;/p&gt;

&lt;p&gt;If you are not, Helm implicitly uses a “reuse values” strategy.&lt;/p&gt;

&lt;p&gt;Both of these strategies can also be enforced via CLI flags, which we’ll cover below.&lt;/p&gt;

&lt;p&gt;Finally, there is also an important gotcha to know related to reusing values when the values schema in the chart has changed. I will also cover that in the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default behaviour
&lt;/h2&gt;

&lt;p&gt;Let’s go through everything in the form of examples.&lt;/p&gt;

&lt;p&gt;When you simply upgrade a chart to a new version, your previous values will stay in effect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/GzC44v7gZrbDbK7ctMTX8dk_raNB-_HBrlVs19LKlW4/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnBsYWluLXVw/Z3JhZGUucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/GzC44v7gZrbDbK7ctMTX8dk_raNB-_HBrlVs19LKlW4/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnBsYWluLXVw/Z3JhZGUucG5n" alt="Infographic of upgrading to a version without setting new values" width="880" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the “reuse values” strategy I mentioned before.&lt;/p&gt;

&lt;p&gt;But if you also set some values during the upgrade, your previous values actually get wiped out!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/iA2ocVb6fWPbt6RjZVFswpqN9IaWzrC8wHvncaIdwTg/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnNldC12YWx1/ZS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/iA2ocVb6fWPbt6RjZVFswpqN9IaWzrC8wHvncaIdwTg/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnNldC12YWx1/ZS5wbmc" alt="Infographic of upgrading to a version and setting new values" width="880" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the “reset values” strategy I mentioned before. Values are reset to the default values of the new chart version, and any new values you’ve specified on the CLI are merged on top of that.&lt;/p&gt;

&lt;p&gt;This can be quite surprising! You need to be careful not to accidentally undo some customization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explicit behaviour using flags
&lt;/h2&gt;

&lt;p&gt;You can control which strategy to use via CLI flags.&lt;/p&gt;

&lt;p&gt;If you are not setting new values, but want to reset to the chart defaults, you can do so via &lt;code&gt;--reset-values&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/oOrvgYflG8VL4IWAllJsjH4wk2kf1l6r1cJ7bHCdRek/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnJlc2V0LXZh/bHVlcy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/oOrvgYflG8VL4IWAllJsjH4wk2kf1l6r1cJ7bHCdRek/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnJlc2V0LXZh/bHVlcy5wbmc" alt="Infographic of upgrading to a version and resetting values" width="880" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in the opposite case, if you are setting new values but want to merge them to previous values, you can use &lt;code&gt;--reuse-values&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/GImUQQEknOcc_A5Y8XHJnXFIiTv5fVeQNSkM3RNVzMo/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnJldXNlLXZh/bHVlcy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/GImUQQEknOcc_A5Y8XHJnXFIiTv5fVeQNSkM3RNVzMo/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnJldXNlLXZh/bHVlcy5wbmc" alt="Infographic of upgrading to a version without setting new values" width="880" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simple, right?&lt;/p&gt;

&lt;p&gt;Well, this works as long as the values schema hasn’t changed in the new chart version. So, for the most part, all clear! But what if the schema has changed…&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;--reuse-values&lt;/code&gt; and schema change
&lt;/h2&gt;

&lt;p&gt;Sometimes the updated chart has changes in its &lt;code&gt;values.yaml&lt;/code&gt;. For example, some section may have been added or an existing one changed a bit. You would expect these changes to be merged into your new release, right?&lt;/p&gt;

&lt;p&gt;And so they are! But only if you’re not using &lt;code&gt;--reuse-values&lt;/code&gt;. Which is confusing, because it makes the flag behave differently from our first example which works almost like &lt;code&gt;--reuse-values&lt;/code&gt; but also merges in changes from the chart.&lt;/p&gt;

&lt;p&gt;So, to clarify… when you do a simple upgrade without setting any values or flags, the updated &lt;code&gt;values.yaml&lt;/code&gt; from the new chart version is merged to your release as expected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/gUvAXUgJN6UkM-7Bk9SWD1K6Nfpz994zKtnPVmpCJSU/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnNjaGVtYS1j/aGFuZ2UucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/gUvAXUgJN6UkM-7Bk9SWD1K6Nfpz994zKtnPVmpCJSU/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnNjaGVtYS1j/aGFuZ2UucG5n" alt="Infographic of upgrading to a version with schema changes" width="880" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But when you want to also update one of the previously set values at the same time, you will naturally use &lt;code&gt;--reuse-values&lt;/code&gt; and &lt;code&gt;--set&lt;/code&gt;, right? Well, in this case &lt;code&gt;--reuse-values&lt;/code&gt; has an unexpected effect. It causes Helm to quite literally “reuse values” from your previous release as the base, disregarding any changes that may have happened in the new chart version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/uZXjaGSW4ve9ZwabIuAaUzVJuzmjsa-hKVW7n4n0rYM/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnNjaGFtZS1j/aGFuZ2UtcmV1c2Uu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/uZXjaGSW4ve9ZwabIuAaUzVJuzmjsa-hKVW7n4n0rYM/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnNjaGFtZS1j/aGFuZ2UtcmV1c2Uu/cG5n" alt="Infographic of upgrading to a version with schema changes and with --reuse-values" width="880" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not the logic you expected, right? You did explicitly ask to reuse values, sure, but why would you expect them not to be merged upon the (updated) default values in the chart first? Especially because that’s the usual behaviour in helm upgrade.&lt;/p&gt;

&lt;p&gt;At least to me this is very unintuitive, because I would expect a package manager to use the chart values as a base, then merge upon it my previous release values, and finally any additional values I’ve supplied with the upgrade.&lt;/p&gt;

&lt;p&gt;Your upgrade may fail when this happens, because the templates are likely to fail with your out-of-date values structure. For example, if a new config section has been added to the chart, but your release values actually lacks that entire section, you’ll surely bump into some nil pointer errors.&lt;/p&gt;

&lt;p&gt;Why is the flag like this? Quoting one of the maintainers from GitHub: “The design goal is to prevent changes from a new chart release’s values from automatically being applied.” Meaning, it is intentional (not a bug) and the behaviour of these flags will not be changed.&lt;/p&gt;

&lt;p&gt;It is still very confusing, because the behaviour between those seemingly similar commands is so unintuitive and can cause your upgrade to break.&lt;/p&gt;

&lt;p&gt;As for alternatives, there is an active &lt;a href="https://github.com/helm/helm/issues/8085"&gt;GitHub issue&lt;/a&gt; from May 2020 with discussion about workarounds and the possibility of a new feature. A new flag like &lt;code&gt;--reset-then-reuse-values&lt;/code&gt; could be a future addition to Helm to clear this up (there is an open &lt;a href="https://github.com/helm/helm/pull/9653"&gt;PR&lt;/a&gt; too).&lt;/p&gt;

&lt;p&gt;In the meantime, if you want the actually expected &lt;code&gt;--reuse-values&lt;/code&gt; behaviour when the chart values have possibly changed, you should dump your old values into a file first, and then use it as a base without &lt;code&gt;--reuse-values&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm get values example-loki &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; prev-values.yaml
helm upgrade example-loki &lt;span class="nt"&gt;-f&lt;/span&gt; prev-values.yaml &lt;span class="nt"&gt;--set&lt;/span&gt; grafana.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way your previous values are merged to the updated chart values, and finally any additional values from the CLI are merged on top of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Knowing how these flags work should surely help you reduce debugging time when running helm upgrade. I hope that this article has been helpful for you in illustrating the differences between them.&lt;/p&gt;

&lt;p&gt;I’ve tried to massage the information into this table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/wd58GexNZd6mw4Ch8toP587iNk7yLv4ZLSwxgy2TiS0/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnRhYmxlLnBu/Zw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/wd58GexNZd6mw4Ch8toP587iNk7yLv4ZLSwxgy2TiS0/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLnRhYmxlLnBu/Zw" alt="Table of helm upgrade scenarios" width="880" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you ever need to figure out a specific scenario, I recommend creating two local charts via &lt;code&gt;helm create test-chart-1&lt;/code&gt; and &lt;code&gt;helm create test-chart-2&lt;/code&gt; and upgrading between them, e.g. &lt;code&gt;helm install test test-chart-1 --set foo=bar &amp;amp;&amp;amp; helm upgrade test test-chart-2 --reset-values &amp;amp;&amp;amp; helm get values --all test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I’ve also gathered all the infographics from this article into one big image, so you can pan and zoom around it if that helps you notice the differences:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://shipmight.com/articles/helm-upgrade-article.infographic.png"&gt;&lt;img src="https://community.ops.io/images/wT-trZEXJhX5d5cXbKZwtASaMRDg4LKw4gFLE9hxwfI/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2hlbG0t/dXBncmFkZS1hcnRp/Y2xlLmluZm9ncmFw/aGljLnBuZw" alt="Infographic of upgrading to a version without setting new values" width="880" height="981"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href="https://shipmight.com"&gt;shipmight.com&lt;/a&gt;. Shipmight is a self-hosted PaaS powered by Kubernetes. Check it out if that sounds interesting!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Link to original post: &lt;a href="https://shipmight.com/articles/understanding-helm-upgrade-reset-reuse-values"&gt;https://shipmight.com/articles/understanding-helm-upgrade-reset-reuse-values&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>helm</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>The absolute beginner’s guide to Docker</title>
      <dc:creator>Martti Laine</dc:creator>
      <pubDate>Thu, 26 May 2022 10:16:59 +0000</pubDate>
      <link>https://community.ops.io/codeclown/the-absolute-beginners-guide-to-docker-5ea</link>
      <guid>https://community.ops.io/codeclown/the-absolute-beginners-guide-to-docker-5ea</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href="https://shipmight.com"&gt;shipmight.com&lt;/a&gt;. Shipmight is a self-hosted PaaS powered by Kubernetes. Check it out if that sounds interesting!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Link to original post: &lt;a href="https://shipmight.com/articles/beginners-guide-docker"&gt;https://shipmight.com/articles/beginners-guide-docker&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The purpose of this tutorial is to illustrate, to a complete beginner, how Docker works and how it can radically simplify their development environment and dependency management. Focus is on the very basics.&lt;/p&gt;

&lt;p&gt;We'll talk enough about how Docker and containers work and what they mean, but we'll keep it at a general and, most importantly, practical level.&lt;/p&gt;

&lt;p&gt;In the end of the tutorial we'll also note some helpful extra commands, including cleaning up images and containers from your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;
Containers and Docker, a short terminology&lt;/li&gt;
&lt;li&gt;
Building a very basic image, just to understand what an image is&lt;/li&gt;
&lt;li&gt;Docker Hub, continuous processes and detached containers&lt;/li&gt;
&lt;li&gt;Ports and volumes&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;
Where to go from here, including ready-to-use examples&lt;/li&gt;
&lt;li&gt;
Other helpful commands when getting started, including a cheatsheet&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Download Docker &lt;a href="https://www.docker.com/products/docker-desktop"&gt;here&lt;/a&gt; for Mac or Windows (note that we will be using some Unix commands in this tutorial; Windows users may still be able to follow)&lt;/li&gt;
&lt;li&gt;You should be familiar with basic Unix commands and comfortable with using the terminal&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Containers and Docker
&lt;/h2&gt;

&lt;p&gt;Let's start by clearing up the concepts and terminology:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Containers&lt;/strong&gt; are isolated parts of your operating system. They are almost like virtual machines. The difference is that they share a lot of resources, like the kernel, with the host operating system, whereas virtual machines enclose their own operating systems completely. Containers are much lighter to set up and run, but they are just as sufficient for running isolated software.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simplified comparison of containers and virtual machines:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/ScE50c86SlhA24NED_viW6GFyRVYwILhw2T2PwV0YHc/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2RvY2tl/ci1hcnRpY2xlLmNv/bnRhaW5lcnMtdm1z/LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/ScE50c86SlhA24NED_viW6GFyRVYwILhw2T2PwV0YHc/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2RvY2tl/ci1hcnRpY2xlLmNv/bnRhaW5lcnMtdm1z/LnBuZw" alt="Diagram comparing containers and VMs in an OS" width="880" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt; is a suite of tools for configuring, running and managing containers. The main command line tool, &lt;code&gt;docker&lt;/code&gt;, can be used to quickly configure and start containers using pre-built images. The suite also includes tools like &lt;code&gt;docker compose&lt;/code&gt; (previously a separate command called &lt;code&gt;docker-compose&lt;/code&gt;, now included as a subcommand of &lt;code&gt;docker&lt;/code&gt;), which is used to quickly start and stop a specific configuration of multiple containers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Images&lt;/strong&gt; are pre-built containers for Docker. In virtual machine land they would be comparable to VM snapshots. Anyone can build an image and then share it, and others will be able to run it without having to build it themselves. Also, images can be extended.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Containers and container images are not exclusive to Docker, and can be used without Docker! While Docker really brought containers to the everyday toolkit of developers, there have been other similar tools developed (e.g. podman). At the time of writing, however, Docker is still the most popular and widely used container platform used by developers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Building a very basic image
&lt;/h2&gt;

&lt;p&gt;Images are a core concept of Docker. The first thing we want to do, in order to fully grasp all the upcoming topics, is to build a very basic image from scratch.&lt;/p&gt;

&lt;p&gt;Images are built using &lt;code&gt;docker build&lt;/code&gt;. The command takes in a single file, &lt;code&gt;Dockerfile&lt;/code&gt;, which it reads step-by-step to configure the image.&lt;/p&gt;

&lt;p&gt;The beauty of Docker is that you can build an image on any machine, like your own computer, and it can be used on any other computer which has Docker installed. This makes Docker great for packaging dependencies and software without worrying about what operating system everyone is using and if they have conflicting dependencies installed.&lt;/p&gt;

&lt;p&gt;Let's build an image right now. You need to have Docker installed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The following commands affect your local Docker installation only, and all resources we create here can be easily removed. Nothing apart from Docker itself is installed on your machine directly. Everything else goes into disposable containers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To prepare, create a new directory and an empty &lt;code&gt;Dockerfile&lt;/code&gt; in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/docker-tutorial
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/docker-tutorial
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;Dockerfile
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls
&lt;/span&gt;Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, write the following contents in &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.7&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt;  MESSAGE  "Hello from Docker!"&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt;  echo $MESSAGE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(You may have noticed that I've used whitespace to align the columns of text. This is not required, but is common practice to make the file more readable.)&lt;/p&gt;

&lt;p&gt;Let's break it down line by line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;FROM alpine:3.7&lt;/code&gt; tells Docker to use version 3.7 of &lt;a href="https://hub.docker.com/_/alpine/"&gt;&lt;code&gt;alpine&lt;/code&gt;&lt;/a&gt; as our base image. &lt;code&gt;alpine&lt;/code&gt; is a minimal Linux distribution, and is a great starting point for any custom image. Most pre-built images you'll find online (like &lt;a href="https://github.com/nginxinc/docker-nginx/blob/master/stable/alpine/Dockerfile"&gt;nginx&lt;/a&gt;) are based on it. You could also base your image on e.g. &lt;a href="https://hub.docker.com/_/ubuntu/"&gt;&lt;code&gt;ubuntu&lt;/code&gt;&lt;/a&gt;. This is a cool feature of Docker: you can easily pick an existing image, of any level of complexity, and just extend it to your needs. See &lt;a href="https://docs.docker.com/engine/reference/builder/#from"&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ENV MESSAGE "Hello from Docker!"&lt;/code&gt; sets an environment variable inside the container. We simply set the value &lt;code&gt;Hello from Docker!&lt;/code&gt; to the environment variable &lt;code&gt;MESSAGE&lt;/code&gt;. The value gets "hardcoded" into the image, and so any program running inside the container after this step has this environment value in their environment. See &lt;a href="https://docs.docker.com/engine/reference/builder/#env"&gt;&lt;code&gt;ENV&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CMD echo $MESSAGE&lt;/code&gt; sets a default command to execute when a container from this image is started. See &lt;a href="https://docs.docker.com/engine/reference/builder/#cmd"&gt;&lt;code&gt;CMD&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other useful instructions would be &lt;a href="https://docs.docker.com/engine/reference/builder/#copy"&gt;&lt;code&gt;COPY&lt;/code&gt;&lt;/a&gt; for copying files from the host machine to the container filesystem, &lt;a href="https://docs.docker.com/engine/reference/builder/#run"&gt;&lt;code&gt;RUN&lt;/code&gt;&lt;/a&gt; for running commands (such as apt-get) inside the container and &lt;a href="https://docs.docker.com/engine/reference/builder/#workdir"&gt;&lt;code&gt;WORKDIR&lt;/code&gt;&lt;/a&gt; for setting the working directory inside the container. See the &lt;a href="https://docs.docker.com/engine/reference/builder/"&gt;reference&lt;/a&gt; for all available instructions.&lt;/p&gt;

&lt;p&gt;Let's now use this &lt;code&gt;Dockerfile&lt;/code&gt; to build an image. We'll give it a name of "first-image":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;--tag&lt;/span&gt; first-image &lt;span class="nb"&gt;.&lt;/span&gt;
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM alpine:3.7
3.7: Pulling from library/alpine
5d20c808ce19: Pull &lt;span class="nb"&gt;complete
&lt;/span&gt;Digest: sha256:8421d9a84432575381bfabd248f1eb56f3aa21d9d7cd2511583c68c9b7511d10
Status: Downloaded newer image &lt;span class="k"&gt;for &lt;/span&gt;alpine:3.7
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 6d1ef012b567
Step 2/3 : ENV  MESSAGE  &lt;span class="s2"&gt;"Hello from Docker!"&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;ba8b83cbfd79
Removing intermediate container ba8b83cbfd79
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ca483a1aa3e4
Step 3/3 : CMD  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$MESSAGE&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;352f5b29295d
Removing intermediate container 352f5b29295d
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 4e148bdd477f
Successfully built 4e148bdd477f
Successfully tagged first-image:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice! Docker went through all the lines in out &lt;code&gt;Dockerfile&lt;/code&gt; and performed the operations we had configured. Each step was also cached; when we change things, only the changed layers (identified by the SHA-digests you see in the output above) will be rebuilt.&lt;/p&gt;

&lt;p&gt;Here's an illustration of the process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/ZxVmEhtcnxYBDEYbThX7DoQWNx-OWQmaYp0JEDePkBw/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2RvY2tl/ci1hcnRpY2xlLmJ1/aWxkLXByb2dyZXNz/LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/ZxVmEhtcnxYBDEYbThX7DoQWNx-OWQmaYp0JEDePkBw/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2RvY2tl/ci1hcnRpY2xlLmJ1/aWxkLXByb2dyZXNz/LnBuZw" alt="Diagram of the process of building an image" width="880" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image was built and we can now see it available on our machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image &lt;span class="nb"&gt;ls
&lt;/span&gt;REPOSITORY     TAG       IMAGE ID        CREATED          SIZE
first-image    latest    4e148bdd477f    2 minutes ago    4.21MB
alpine         3.7       6d1ef012b567    2 minutes ago    4.21MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's start a container with our new image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run first-image
Hello from Docker!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is all it took for us to run a command inside a contained Linux distribution, isolated on our machine. You could push this image to an image registry, and your colleague could pull it from there and run it, and they'd get the exact same behaviour. This is how Docker can be used to package software in a reusable manner.&lt;/p&gt;

&lt;p&gt;You can also override the default command. For example, run &lt;code&gt;date&lt;/code&gt; which prints out current date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run first-image &lt;span class="nb"&gt;date
&lt;/span&gt;Thu Jan 16 12:21:14 UTC 2020
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's make the image a bit more complex by installing curl into it and calling a mock API. Update &lt;code&gt;Dockerfile&lt;/code&gt; to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.7&lt;/span&gt;
&lt;span class="c"&gt;# apk is the package manager in alpine (same as apt-get in debian/ubuntu)&lt;/span&gt;
&lt;span class="k"&gt;RUN  &lt;/span&gt;apk &lt;span class="nt"&gt;--no-cache&lt;/span&gt; add curl
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt;  MESSAGE  "Hello from Docker!"&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt;  curl -X POST --data-raw "$MESSAGE" -s https://postman-echo.com/post&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's build it again, using the name "second-image":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; second-image &lt;span class="nb"&gt;.&lt;/span&gt;
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM alpine:3.7
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 6d1ef012b567
Step 2/4 : RUN  apk &lt;span class="nt"&gt;--no-cache&lt;/span&gt; add curl
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;18729752a4c4
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
&lt;span class="o"&gt;(&lt;/span&gt;1/4&lt;span class="o"&gt;)&lt;/span&gt; Installing ca-certificates &lt;span class="o"&gt;(&lt;/span&gt;20190108-r0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;2/4&lt;span class="o"&gt;)&lt;/span&gt; Installing libssh2 &lt;span class="o"&gt;(&lt;/span&gt;1.9.0-r1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;3/4&lt;span class="o"&gt;)&lt;/span&gt; Installing libcurl &lt;span class="o"&gt;(&lt;/span&gt;7.61.1-r3&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;4/4&lt;span class="o"&gt;)&lt;/span&gt; Installing curl &lt;span class="o"&gt;(&lt;/span&gt;7.61.1-r3&lt;span class="o"&gt;)&lt;/span&gt;
Executing busybox-1.27.2-r11.trigger
Executing ca-certificates-20190108-r0.trigger
OK: 6 MiB &lt;span class="k"&gt;in &lt;/span&gt;17 packages
Removing intermediate container 18729752a4c4
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 310020ae5bc4
Step 3/4 : ENV  MESSAGE  &lt;span class="s2"&gt;"Hello from Docker!"&lt;/span&gt;
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;ca3f999a8e39
Removing intermediate container ca3f999a8e39
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 7de9a60e37f0
Step 4/4 : CMD  curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;--data-raw&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MESSAGE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; https://postman-echo.com/post
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Running &lt;span class="k"&gt;in &lt;/span&gt;7fef1818a84f
Removing intermediate container 7fef1818a84f
 &lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; e43e70afd694
Successfully built e43e70afd694
Successfully tagged second-image:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the updated image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run second-image
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"args"&lt;/span&gt;:&lt;span class="o"&gt;{}&lt;/span&gt;,&lt;span class="s2"&gt;"data"&lt;/span&gt;:&lt;span class="s2"&gt;""&lt;/span&gt;,&lt;span class="s2"&gt;"files"&lt;/span&gt;:&lt;span class="o"&gt;{}&lt;/span&gt;,&lt;span class="s2"&gt;"form"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Hello from Docker!"&lt;/span&gt;:&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"headers"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"x-forwarded-proto"&lt;/span&gt;:&lt;span class="s2"&gt;"https"&lt;/span&gt;,&lt;span class="s2"&gt;"host"&lt;/span&gt;:&lt;span class="s2"&gt;"postman-echo.com"&lt;/span&gt;,&lt;span class="s2"&gt;"content-length"&lt;/span&gt;:&lt;span class="s2"&gt;"18"&lt;/span&gt;,&lt;span class="s2"&gt;"accept"&lt;/span&gt;:&lt;span class="s2"&gt;"*/*"&lt;/span&gt;,&lt;span class="s2"&gt;"content-type"&lt;/span&gt;:&lt;span class="s2"&gt;"application/x-www-form-urlencoded"&lt;/span&gt;,&lt;span class="s2"&gt;"user-agent"&lt;/span&gt;:&lt;span class="s2"&gt;"curl/7.61.1"&lt;/span&gt;,&lt;span class="s2"&gt;"x-forwarded-port"&lt;/span&gt;:&lt;span class="s2"&gt;"443"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"json"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Hello from Docker!"&lt;/span&gt;:&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"url"&lt;/span&gt;:&lt;span class="s2"&gt;"https://postman-echo.com/post"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works as expected!&lt;/p&gt;

&lt;p&gt;At this point let's list our Docker containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps &lt;span class="nt"&gt;--all&lt;/span&gt;
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS                          PORTS               NAMES
72b572fa3ccd        second-image        &lt;span class="s2"&gt;"/bin/sh -c 'curl -X…"&lt;/span&gt;   About a minute ago   Exited &lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt; About a minute ago                       strange_napier
d98a14f4d10d        first-image         &lt;span class="s2"&gt;"date"&lt;/span&gt;                   8 minutes ago        Exited &lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt; 8 minutes ago                            condescending_fermat
7af8a001af1c        first-image         &lt;span class="s2"&gt;"/bin/sh -c 'echo &lt;/span&gt;&lt;span class="nv"&gt;$M&lt;/span&gt;&lt;span class="s2"&gt;…"&lt;/span&gt;   11 minutes ago       Exited &lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt; 11 minutes ago                           ecstatic_leavitt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Earlier we used the command &lt;code&gt;docker image ls&lt;/code&gt;, which lists images that are available in the machine. The last command, &lt;code&gt;docker ps&lt;/code&gt;, lists containers which have been started using those images.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As you can see, each line says "Exited X minutes ago". The containers were started but they are not running anymore (we'll cover continuous processes in the next section). We can remove the unused containers like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;rm &lt;/span&gt;strange_napier condescending_fermat ecstatic_leavitt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;docker&lt;/code&gt; has autocomplete for bash, so you can just type &lt;code&gt;docker rm [TAB]&lt;/code&gt; and the container names will be suggested.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the future, when we run containers like these, we might want to specify the &lt;code&gt;--rm&lt;/code&gt; option so that Docker will automatically remove the container after it stops. Like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; second-image
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"args"&lt;/span&gt;:&lt;span class="o"&gt;{}&lt;/span&gt;,&lt;span class="s2"&gt;"data"&lt;/span&gt;:&lt;span class="s2"&gt;""&lt;/span&gt;,&lt;span class="s2"&gt;"files"&lt;/span&gt;:&lt;span class="o"&gt;{}&lt;/span&gt;,&lt;span class="s2"&gt;"form"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Hello from Docker!"&lt;/span&gt;:&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"headers"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"x-forwarded-proto"&lt;/span&gt;:&lt;span class="s2"&gt;"https"&lt;/span&gt;,&lt;span class="s2"&gt;"host"&lt;/span&gt;:&lt;span class="s2"&gt;"postman-echo.com"&lt;/span&gt;,&lt;span class="s2"&gt;"content-length"&lt;/span&gt;:&lt;span class="s2"&gt;"18"&lt;/span&gt;,&lt;span class="s2"&gt;"accept"&lt;/span&gt;:&lt;span class="s2"&gt;"*/*"&lt;/span&gt;,&lt;span class="s2"&gt;"content-type"&lt;/span&gt;:&lt;span class="s2"&gt;"application/x-www-form-urlencoded"&lt;/span&gt;,&lt;span class="s2"&gt;"user-agent"&lt;/span&gt;:&lt;span class="s2"&gt;"curl/7.61.1"&lt;/span&gt;,&lt;span class="s2"&gt;"x-forwarded-port"&lt;/span&gt;:&lt;span class="s2"&gt;"443"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"json"&lt;/span&gt;:&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Hello from Docker!"&lt;/span&gt;:&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;,&lt;span class="s2"&gt;"url"&lt;/span&gt;:&lt;span class="s2"&gt;"https://postman-echo.com/post"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps &lt;span class="nt"&gt;--all&lt;/span&gt;
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No containers were listed, as we hoped for! Docker removed the container automatically after it had finished execution.&lt;/p&gt;

&lt;p&gt;You also probably noticed the strange names of your containers. They were auto-generated by Docker. You can specify a name for a container by using the &lt;code&gt;--name&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; my-container second-image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great. Let's move on to containers that stay running in the background...&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Hub, continuous processes and detached containers
&lt;/h2&gt;

&lt;p&gt;In the previous section we built our own custom image. In this one, we'll utilize the powerful &lt;a href="https://hub.docker.com/search?q=&amp;amp;type=image"&gt;Docker Hub&lt;/a&gt;, which contains pre-configured images for nearly any software you might need in your projects.&lt;/p&gt;

&lt;p&gt;For example, this is all it takes to start an isolated Postgres database on our machine, using the &lt;a href="https://hub.docker.com/_/postgres"&gt;&lt;code&gt;postgres&lt;/code&gt; image&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; my-postgres postgres
...
2020-01-16 12:34:40.853 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on IPv4 address &lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;, port 5432
2020-01-16 12:34:40.854 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on IPv6 address &lt;span class="s2"&gt;"::"&lt;/span&gt;, port 5432
2020-01-16 12:34:40.863 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on Unix socket &lt;span class="s2"&gt;"/var/run/postgresql/.s.PGSQL.5432"&lt;/span&gt;
2020-01-16 12:34:40.898 UTC &lt;span class="o"&gt;[&lt;/span&gt;55] LOG:  database system was shut down at 2020-01-16 12:34:40 UTC
2020-01-16 12:34:40.930 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  database system is ready to accept connections
 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As with any terminal command, you can terminate the process by pressing Ctrl+C.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Docker first pulled the image from Docker Hub, and then created and started a container with it.&lt;/p&gt;

&lt;p&gt;You can run many of these containers at the same time. While the old one is running, open another tab in your terminal and start a second one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; another-postgres postgres
...
2020-01-16 12:34:40.853 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on IPv4 address &lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;, port 5432
2020-01-16 12:34:40.854 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on IPv6 address &lt;span class="s2"&gt;"::"&lt;/span&gt;, port 5432
2020-01-16 12:34:40.863 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on Unix socket &lt;span class="s2"&gt;"/var/run/postgresql/.s.PGSQL.5432"&lt;/span&gt;
2020-01-16 12:34:40.898 UTC &lt;span class="o"&gt;[&lt;/span&gt;55] LOG:  database system was shut down at 2020-01-16 12:34:40 UTC
2020-01-16 12:34:40.930 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  database system is ready to accept connections
 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Even if both containers logged "listening on port 5432", they are actually listening inside the containers, not on your host machine. So there is no port collision. In the next section we'll learn how to expose ports to the host machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You now have two Postgres databases running on the same machine, without having to install anything else but Docker on your computer. The containers don't have access to your filesystem and only make changes inside their own. How neat is that!&lt;/p&gt;

&lt;p&gt;Notice that the second time Docker didn't have to pull the &lt;code&gt;postgres&lt;/code&gt; image again. It was already available on your machine. As mentioned before, you can list all the available images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image &lt;span class="nb"&gt;ls
&lt;/span&gt;REPOSITORY     TAG       IMAGE ID        CREATED          SIZE
postgres       latest    30121e967865    2 minutes ago    289MB
second-image   latest    224c7ee73e67    5 minutes ago    5.6MB
first-image    latest    4e148bdd477f    8 minutes ago    4.21MB
alpine         3.7       6d1ef012b567    8 minutes ago    4.21MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These Postgres instances keep running until you press Ctrl+C. This isn't very practical if you want to run a database in the backgrond. The solution is to run it in detached mode by setting the &lt;code&gt;--detach&lt;/code&gt; (or &lt;code&gt;-d&lt;/code&gt;) option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; my-postgres &lt;span class="nt"&gt;--detach&lt;/span&gt; postgres
4f18a479c6e261f631c18f43b9facb9d99c80da4a7acee7aebe7edd5411b4bc3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the container was started and it is still running, but in the background (the output from the command is its ID). You can see it by listing all running containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
4f18a479c6e2        postgres            &lt;span class="s2"&gt;"docker-entrypoint.s…"&lt;/span&gt;   50 seconds ago      Up 48 seconds       5432/tcp            my-postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can view its console output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 10 my-postgres
&lt;span class="k"&gt;done
&lt;/span&gt;server stopped

PostgreSQL init process &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; ready &lt;span class="k"&gt;for &lt;/span&gt;start up.

2020-01-16 12:41:19.268 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on IPv4 address &lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;, port 5432
2020-01-16 12:41:19.268 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on IPv6 address &lt;span class="s2"&gt;"::"&lt;/span&gt;, port 5432
2020-01-16 12:41:19.271 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  listening on Unix socket &lt;span class="s2"&gt;"/var/run/postgresql/.s.PGSQL.5432"&lt;/span&gt;
2020-01-16 12:41:19.286 UTC &lt;span class="o"&gt;[&lt;/span&gt;55] LOG:  database system was shut down at 2020-01-16 12:41:19 UTC
2020-01-16 12:41:19.291 UTC &lt;span class="o"&gt;[&lt;/span&gt;1] LOG:  database system is ready to accept connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can execute a command inside it (substitute &lt;code&gt;whoami&lt;/code&gt; with your command):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;my-postgres &lt;span class="nb"&gt;whoami
&lt;/span&gt;root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Processes inside Docker containers run as root by default. This can (and should!) be changed per-image via the &lt;a href="https://docs.docker.com/engine/reference/builder/#user"&gt;&lt;code&gt;USER&lt;/code&gt;&lt;/a&gt; instruction in Dockerfile.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can stop it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker stop my-postgres
my-postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you can remove it (add &lt;code&gt;--force&lt;/code&gt; or &lt;code&gt;-f&lt;/code&gt; to force removal if it's running):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;rm &lt;/span&gt;my-postgres
my-postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can usually find a premade image for any software. Simply google for "&lt;em&gt;software&lt;/em&gt; docker". For example, here's a bunch of ready-to-use, popular images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/postgres"&gt;&lt;code&gt;postgres&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/mysql"&gt;&lt;code&gt;mysql&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/nginx"&gt;&lt;code&gt;nginx&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/minio/minio/"&gt;&lt;code&gt;minio/minio&lt;/code&gt; (self-hosted version of AWS S3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/redis"&gt;&lt;code&gt;redis&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can try any of these by simply running &lt;code&gt;docker run &amp;lt;image&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ports and volumes
&lt;/h2&gt;

&lt;p&gt;Above we started some containers, but didn't really communicate with them. In most projects there are two types of communication you would want to do with your software dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network&lt;/strong&gt;, for example connecting to a Postgres database at a specific port&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filesystem&lt;/strong&gt;, for example reading and writing nginx configuration files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's very easy to achieve both in Docker.&lt;/p&gt;

&lt;p&gt;For network access, we can configure shared ports for containers. For example, Postgres by default listens to port 5432. We can expose this port to our host machine via the &lt;code&gt;--publish&lt;/code&gt; (or &lt;code&gt;-p&lt;/code&gt;) option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; my-postgres &lt;span class="nt"&gt;--publish&lt;/span&gt; 5432:5432 postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above we tell Docker to map the port 5432 on our host machine to the port 5432 inside the container. You can try it if you have &lt;code&gt;psql&lt;/code&gt; (the Postgres client) installed on your host machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;psql postgres://postgres:postgres@localhost:5432/postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The username, password and database name are all defaults ("postgres"), as is documented on the &lt;a href="https://hub.docker.com/_/postgres"&gt;Docker Hub page&lt;/a&gt;. We will learn how to customize them in the next section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For filesystem access, we can tell Docker to &lt;em&gt;bind mount&lt;/em&gt; a specific directory (or file) to a location inside the container. For example, we could persist the Postgres data directory on our host machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt; my-postgres &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--volume&lt;/span&gt; /path/to/docker-tutorial/postgres-data:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
    postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when inside the container Postgres writes its data to &lt;code&gt;/var/lib/postgresql/data&lt;/code&gt;, the files are actually stored on your host machine at &lt;code&gt;/path/to/docker-tutorial/postgres-data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or we could substitute the nginx configuration file with our own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt; my-nginx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--volume&lt;/span&gt; /path/to/custom/nginx.conf:/etc/nginx/conf.d/default.conf &lt;span class="se"&gt;\&lt;/span&gt;
    nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is also possible to specify &lt;code&gt;read-only&lt;/code&gt; access for the container by adding &lt;code&gt;:ro&lt;/code&gt;, if necessary. In that case the container can't write to the mounted location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt; my-nginx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--volume&lt;/span&gt; /path/to/custom/nginx.conf:/etc/nginx/conf.d/default.conf:ro &lt;span class="se"&gt;\&lt;/span&gt;
    nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In these examples we mounted locations to actual locations on the host machine by specifying the absolute path to them. Docker also supports Docker volumes, which are storage volumes managed via docker commands. You can create a volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker volume create my-postgres-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the use it by its name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--name&lt;/span&gt; my-postgres &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--volume&lt;/span&gt; my-postgres-data:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
    postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes Docker volumes are actually just directories created by Docker. They are stored in a hidden folder (e.g. &lt;code&gt;/var/lib/docker/volumes&lt;/code&gt; in Linux).&lt;/p&gt;

&lt;p&gt;You can choose to use bind mounts or Docker volumes based on your preference. Bind mounts are perhaps easier to understand and inspect in the beginning, because you have to specify a concrete location for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment variables
&lt;/h2&gt;

&lt;p&gt;If a container expects custom configuration, it is usually done via environment variables (&lt;code&gt;--env&lt;/code&gt; or &lt;code&gt;-e&lt;/code&gt;). For example, we can customize the Postgres user, password and database name when starting the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;foobar &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret123 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_database &lt;span class="se"&gt;\&lt;/span&gt;
    postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such configuration varies based on the image, and is usually documented on the Docker Hub page for that image. Search for "Environment Variables" on the &lt;a href="https://hub.docker.com/_/postgres"&gt;&lt;code&gt;postgres&lt;/code&gt; image page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;In the beginning you'll probably find Docker more useful for deploying your development dependencies than building your own images.&lt;/p&gt;

&lt;p&gt;The simplest way to get going is to just use the &lt;code&gt;docker&lt;/code&gt; command in your next project to start third-party dependencies. You will probably find it faster and more convenient to manage simultaneous database instances, etc., than what you were using before.&lt;/p&gt;

&lt;p&gt;Some useful examples to get started with databases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="c"&gt;# connection string: postgres://postgres:postgres@localhost:5432/postgres&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="c"&gt;# add to persist data on host: `-v /path/on/host:/var/lib/postgresql/data`&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 postgres

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="c"&gt;# connection string: mysql://example:secret123@localhost:3305&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="c"&gt;# add to persist data on host: `-v /path/on/host:/var/lib/mysql`&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3306:3306 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;super_secret123 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;example &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret123 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_database &lt;span class="se"&gt;\&lt;/span&gt;
    mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you're comfortable with starting and managing containers manually, the next step could be to specify your development environment in a file called &lt;code&gt;docker-compose.yml&lt;/code&gt; and to use the &lt;code&gt;docker compose&lt;/code&gt; command to start/stop dependencies. This way anyone can clone your project source code, run &lt;code&gt;docker compose up&lt;/code&gt; and be ready to start developing. Here's an example &lt;code&gt;docker-compose.yml&lt;/code&gt;, taken directly from &lt;a href="https://hub.docker.com/_/postgres"&gt;&lt;code&gt;postgres&lt;/code&gt; Docker Hub page&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.1"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
  &lt;span class="c1"&gt;# Web admin interface for SQL databases, similar to PhpMyAdmin&lt;/span&gt;
  &lt;span class="na"&gt;adminer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adminer&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could add other services you need into the specification, then just run &lt;code&gt;docker compose up&lt;/code&gt; and Docker will start the new ones and remove the old ones. Running &lt;code&gt;docker compose down&lt;/code&gt; will remove all containers and their volumes. Try it and you will see it is a very efficient way to set up and share local development environments per project for your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other helpful commands when getting started
&lt;/h2&gt;

&lt;p&gt;Cheatsheet for common operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;host_port&amp;gt;:&amp;lt;container_port&amp;gt; &amp;lt;image&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;value &amp;lt;image&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /host/path:/container/path &amp;lt;image&amp;gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps
&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker logs &amp;lt;container&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 10 &amp;lt;container&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; &amp;lt;container&amp;gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;container&amp;gt; &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &amp;lt;signal&amp;gt; &amp;lt;container&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; HUP &amp;lt;container&amp;gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker start &amp;lt;container&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker restart &amp;lt;container&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker stop &amp;lt;container&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &amp;lt;container&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove all containers, including running and not-running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove any images that are not used currently by any container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker image prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove any volumes that are not used currently by any container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker volume prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an image version you can save to your disk:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/t-YFEH9q8e_Cl8o2bDTNS2dmoOrXIDT8N2HGp3IV49A/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2RvY2tl/ci1hcnRpY2xlLmNo/ZWF0c2hlZXQucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/t-YFEH9q8e_Cl8o2bDTNS2dmoOrXIDT8N2HGp3IV49A/w:880/mb:500000/ar:1/aHR0cHM6Ly9zaGlw/bWlnaHQuY29tL2Fy/dGljbGVzL2RvY2tl/ci1hcnRpY2xlLmNo/ZWF0c2hlZXQucG5n" alt="Docker cheatsheet" width="880" height="587"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href="https://shipmight.com"&gt;shipmight.com&lt;/a&gt;. Shipmight is a self-hosted PaaS powered by Kubernetes. Check it out if that sounds interesting!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Link to original post: &lt;a href="https://shipmight.com/articles/beginners-guide-docker"&gt;https://shipmight.com/articles/beginners-guide-docker&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>tutorials</category>
      <category>beginners</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
