<?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>DEV Community: HZ</title>
    <description>The latest articles on DEV Community by HZ (@zavrelj).</description>
    <link>https://dev.to/zavrelj</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F277072%2Fbdece5bf-6345-4179-9889-7d3b3bd09859.png</url>
      <title>DEV Community: HZ</title>
      <link>https://dev.to/zavrelj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zavrelj"/>
    <language>en</language>
    <item>
      <title>CSS Conflicts</title>
      <dc:creator>HZ</dc:creator>
      <pubDate>Fri, 08 May 2020 06:33:22 +0000</pubDate>
      <link>https://dev.to/zavrelj/css-conflicts-4k96</link>
      <guid>https://dev.to/zavrelj/css-conflicts-4k96</guid>
      <description>&lt;p&gt;In this article, I will explain how CSS conflicts are resolved automatically in the browser, what are the concepts for solving issues with contradicting declarations and how to quickly override the default behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you prefer video?
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Lh3caSGghpY"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;There are three possible places where you can have your CSS styles defined.&lt;/p&gt;

&lt;p&gt;First, it can be inside the &lt;strong&gt;&amp;lt;head&amp;gt;&lt;/strong&gt; element of the HTML document you want to style. In such case, you need to wrap your stylesheet with the &lt;strong&gt;&amp;lt;style&amp;gt;&lt;/strong&gt; element. &lt;/p&gt;

&lt;p&gt;Second, it can be right inside the specific HTML tag. In such case, you need to have your style defined as a value of the &lt;strong&gt;style&lt;/strong&gt; argument of that HTML tag. This is not very useful for big stylesheets.&lt;/p&gt;

&lt;p&gt;Third, it can be stored in a separate file which is linked to the HTML document you want to style. In such case, you use the &lt;strong&gt;&amp;lt;link&amp;gt;&lt;/strong&gt; element placed inside the &lt;strong&gt;&amp;lt;head&amp;gt;&lt;/strong&gt; element.&lt;/p&gt;

&lt;p&gt;But what would happen if you had two or three styles defined for the same document and they contradict each other?&lt;/p&gt;

&lt;h2&gt;
  
  
  I will make you a full-stack web developer in just 12 hours!
&lt;/h2&gt;

&lt;p&gt;This tutorial is a part of a much larger and far more detailed online course available for free on &lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt;. If you're new to Skillshare, you'll get premium access not only to this course for free but to &lt;strong&gt;all of my other courses as well and for a good measure, to over 22.000 courses&lt;/strong&gt; currently hosted on Skillshare. &lt;/p&gt;

&lt;p&gt;You won't be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Consider it a 2 months worth of premium education for free. It's like Netflix, only for life-long learners :-)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodewithjan.com%2Fimages%2Fposts%2Ftwdc-skillshare-cover-play.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can have a CSS rule inside the external file which defines the color of the &lt;strong&gt;&amp;lt;p&amp;gt;&lt;/strong&gt; element as yellow, but at the same time you can have another CSS rule which defines color as red right inside the &lt;strong&gt;&amp;lt;p&amp;gt;&lt;/strong&gt; element.&lt;/p&gt;

&lt;p&gt;What would happen? What would be the color? Yellow or red? That's what we are about to find out now.&lt;/p&gt;

&lt;p&gt;Download the &lt;a href="https://codewithjan.com/resources/css-conflicts.zip" rel="noopener noreferrer"&gt;css-conflicts.zip&lt;/a&gt; to the location of your choice, unpack the archive and open the &lt;strong&gt;css-conflicts.html&lt;/strong&gt; file in the code editor. In this file, there are three definitions of the style for the &lt;strong&gt;&amp;lt;p&amp;gt;&lt;/strong&gt; element.&lt;/p&gt;

&lt;p&gt;The first one is inside the attached &lt;strong&gt;style.css&lt;/strong&gt; file and it says that the color should be yellow. The second one is inside the &lt;strong&gt;&amp;lt;head&amp;gt;&lt;/strong&gt; element and it says that the color should be blue. And finally, we have a third style as an attribute of the &lt;strong&gt;&amp;lt;p&amp;gt;&lt;/strong&gt; element itself which says that the color should be red.&lt;/p&gt;

&lt;p&gt;Now, open the file in the browser to see which color wins. As you can see the color of the text is red, but why?&lt;/p&gt;

&lt;h2&gt;
  
  
  Priority
&lt;/h2&gt;

&lt;p&gt;There are rules that define the priority of application of styles in case of conflicting definitions. It's in the name of CSS, cascading.&lt;/p&gt;

&lt;p&gt;Cascading is the fundamental feature of CSS. It defines how CSS rules from different sources are propagated. Cascading algorithm decides which declaration is applied to any given element and thus solves potential conflicts between declarations.&lt;/p&gt;

&lt;p&gt;There are four concepts for solving issues with contradicting declarations and rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Origin&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Merge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inheritance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Specificity&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at each of them now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Origin
&lt;/h2&gt;

&lt;p&gt;The  &lt;strong&gt;origin precedence&lt;/strong&gt;  rule states that if declarations are in conflict, then the last declaration wins over the previous declarations.&lt;/p&gt;

&lt;p&gt;That means the declaration which is the last one in the code. Since HTML is processed sequentially from top to bottom, the bottom-most declaration overrides all the declarations above it.&lt;/p&gt;

&lt;p&gt;In case of external CSS file, its position in the document is where the link is placed. Think of it as if the whole content of the linked file is inserted instead of the link, thus the whole stylesheet would be a part of the HTML document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Merge
&lt;/h2&gt;

&lt;p&gt;When declarations are not in conflict, they just merge into one big declaration and all properties are applied.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;css-conflicts.html&lt;/strong&gt; file, add the &lt;strong&gt;font-weight&lt;/strong&gt; declaration to the style for the &lt;strong&gt;&amp;lt;p&amp;gt;&lt;/strong&gt; element placed in the &lt;strong&gt;&amp;lt;style&amp;gt;&lt;/strong&gt; element of the document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, go to the  &lt;strong&gt;style.css&lt;/strong&gt; file and set the &lt;strong&gt;background-color&lt;/strong&gt; to yellow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the changes and refresh the page in the browser. You will see the bold red text on the yellow background.&lt;/p&gt;

&lt;p&gt;Even though the conflicting text color declarations have been resolved by applying the last declaration, the rest has been merged together.&lt;/p&gt;

&lt;p&gt;So, we have the background color defined in the external &lt;strong&gt;style.css&lt;/strong&gt; file, bold text defined in the internal stylesheet inside the &lt;strong&gt;&amp;lt;style&amp;gt;&lt;/strong&gt; element of the document and the text color defined directly inside the &lt;strong&gt;&amp;lt;p&amp;gt;&lt;/strong&gt; element.&lt;/p&gt;

&lt;p&gt;To see it in action, just right click the paragraph and select &lt;strong&gt;Inspect&lt;/strong&gt; option to open Chrome Developer Tools in the browser.&lt;/p&gt;

&lt;p&gt;Here, you can see the list of styles applied to the paragraph element along their origin and even which style wasn't applied because it was overridden by another style:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00tb36k2zebo3551q3bi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00tb36k2zebo3551q3bi.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inheritance
&lt;/h2&gt;

&lt;p&gt;If you specify a property for an element, all its descendants will inherit that property.&lt;/p&gt;

&lt;p&gt;But how could the browser know about the descendants of a specific element? That's where the DOM, or Document Object Model comes in the scene.&lt;/p&gt;

&lt;p&gt;The DOM is an interface that treats HTML document as a tree structure. Each node of this tree is an object that represents a part of the document.&lt;/p&gt;

&lt;p&gt;Most web browsers use the internal document object model to render HTML documents. The browser downloads HTML document into local memory and parses it to display the page on the screen.&lt;/p&gt;

&lt;p&gt;Once the browser knows which element is a child and which element is a parent, it's easy to apply the Inheritance concept on HTML elements.&lt;/p&gt;

&lt;p&gt;You can see the example of DOM tree at the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbqv0hcrirykvct6hsmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbqv0hcrirykvct6hsmd.png" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't confuse DOM with BOM. While DOM deals with the HTML elements,&lt;br&gt;
the BOM (Browser Object Model) deals with browser components, such as&lt;br&gt;
history, location, navigator, screen, and so on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Open the &lt;strong&gt;css-inheritance.html&lt;/strong&gt; file in the browser and select  &lt;strong&gt;Inspect&lt;/strong&gt;  to display Developer Tools.&lt;/p&gt;

&lt;p&gt;As you can see, we have a style defined for the &lt;strong&gt;&amp;lt;body&amp;gt;&lt;/strong&gt; element. This style will be inherited by both &lt;strong&gt;&amp;lt;p&amp;gt;&lt;/strong&gt;  elements except for the color of the text of the first &lt;strong&gt;&amp;lt;p&amp;gt;&lt;/strong&gt;  element which is directly specified for it as an inline declaration. &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&amp;lt;h1&amp;gt;&lt;/strong&gt;  element will inherit everything from the &lt;strong&gt;&amp;lt;body&amp;gt;&lt;/strong&gt;  element. We didn't have to specify any style for the &lt;strong&gt;&amp;lt;h1&amp;gt;&lt;/strong&gt;  element, yet it is styled thanks to the inheritance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtsban5fas8quisioqwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtsban5fas8quisioqwz.png" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Specificity
&lt;/h2&gt;

&lt;p&gt;Specificity concept is based on the rule that the most specific selector combination wins. Selectors have different scores and the highest total score of selectors (or their combinations) wins.&lt;/p&gt;

&lt;p&gt;The highest score is 1000 and we will get it if we specify the style directly inside the tag with inline stylesheet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9itzgnuzculqrez48alf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9itzgnuzculqrez48alf.png" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The lowest score is 0001 which we will get if we define the style for an element. If there are two elements combined, we will get the score 0002, because it has higher specificity:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8delv2ugn1wtlxvpyif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8delv2ugn1wtlxvpyif.png" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try to calculate the score for these two examples to see what color will be assigned to the paragraph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq78kdcfntdvgmqh45ry6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq78kdcfntdvgmqh45ry6.png" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the first example, we have the element selector (&lt;strong&gt;div&lt;/strong&gt;) and the id selector (&lt;strong&gt;#paragraph&lt;/strong&gt;). So, the style gets 0 points, the id gets 1 point, there's no class, pseudo-class or attribute, so this gets 0 points as well. Finally, the number of affected elements is 1 because there's only the &lt;strong&gt;div&lt;/strong&gt; element in the definition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finjuk4badfmt01devtb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finjuk4badfmt01devtb4.png" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the second example, we have the element-class combinator (&lt;strong&gt;div.green&lt;/strong&gt;) and the element selector (&lt;strong&gt;p&lt;/strong&gt;). So, the style gets 0 points again, the id gets 0 points as well, but there's one class, so this gets 1 point. Finally, we have two elements affected, the  &lt;strong&gt;div&lt;/strong&gt; element and the  &lt;strong&gt;p&lt;/strong&gt;  element, so this gets 2 points.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbe22zsrxwdu3iiaw7e3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbe22zsrxwdu3iiaw7e3.png" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on the calculation, the color of the paragraph should be blue, because the definition for the blue color gets 101 points, while the other definition for the green color gets only 12 points.&lt;/p&gt;

&lt;p&gt;Once you have this done, open the &lt;strong&gt;css-specificity.html&lt;/strong&gt; file to see the result. And sure enough, the color of the paragraph is blue indeed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fat4kybd48qlu5v7gyamw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fat4kybd48qlu5v7gyamw.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Important
&lt;/h2&gt;

&lt;p&gt;Sometimes, we don't have time to calculate the score or think about what declaration wins and why. In such cases, the &lt;strong&gt;!important&lt;/strong&gt; rule might be helpful.&lt;/p&gt;

&lt;p&gt;Add a new CSS declaration to your &lt;strong&gt;css-specificity.html&lt;/strong&gt; file right above the closing part of the &lt;strong&gt;&amp;lt;/style&amp;gt;&lt;/strong&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though this is the least specific selector, it doesn't matter because it will be applied anyway.&lt;/p&gt;

&lt;p&gt;Save the changes and refresh the page in the browser to see that the text color of the paragraph will change to red.&lt;/p&gt;

&lt;p&gt;I strongly suggest against using the &lt;strong&gt;!important&lt;/strong&gt; rule though, because it will only add to the mess if you start overriding cascading rules everywhere.&lt;/p&gt;

&lt;p&gt;Use it wisely, and only if you absolutely must.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to learn more?
&lt;/h2&gt;

&lt;p&gt;The rest of this tutorial is available for free on &lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt; as a part of the much larger and far more detailed video course. Again, if you're new to Skillshare, you'll also get premium access to more than 22.000 courses. Remember, that you won't be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Skillshare is Netflix for life-long learners.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodewithjan.com%2Fimages%2Fposts%2Ftwdc-skillshare-cover-play.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>specificity</category>
      <category>inheritance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Mastering SQL Basics</title>
      <dc:creator>HZ</dc:creator>
      <pubDate>Wed, 26 Feb 2020 10:49:16 +0000</pubDate>
      <link>https://dev.to/zavrelj/mastering-sql-basics-1dho</link>
      <guid>https://dev.to/zavrelj/mastering-sql-basics-1dho</guid>
      <description>&lt;p&gt;In this show tutorial, I will show you the basics of Structured Query Language, also known as SQL. We'll install together Database Management System as a part of the LAMP stack via XAMPP and I will explain how to use MySQL Command Line Interface in Terminal (or Command Prompt) to communicate directly with your databases without using any middleman interface like phpMyAdmin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do your prefer video?
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/T9pUFG0B67k"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll learn
&lt;/h2&gt;

&lt;p&gt;Once you're all set, we will inspect together the default MySQL databases that come with the XAMPP installation. &lt;/p&gt;

&lt;p&gt;I will also teach you how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a new database,&lt;/li&gt;
&lt;li&gt;define a new table&lt;/li&gt;
&lt;li&gt;delete existing table&lt;/li&gt;
&lt;li&gt;insert new record&lt;/li&gt;
&lt;li&gt;update existing record&lt;/li&gt;
&lt;li&gt;retrieve table data&lt;/li&gt;
&lt;li&gt;removing table data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will take a look at some special table features like primary keys and auto-incrementation.&lt;/p&gt;

&lt;p&gt;Finally, we will build a simple table of action movies so you can truly understand how to use SQL language in some real-world scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  I will make you a full-stack web developer in just 12 hours!
&lt;/h2&gt;

&lt;p&gt;This tutorial is a part of a much larger and far more detailed online course available for free on &lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt;. If you're new to Skillshare, you'll get premium access not only to this course for free but to &lt;strong&gt;all of my other courses as well and for a good measure, to over 22.000 courses&lt;/strong&gt; currently hosted on Skillshare. &lt;/p&gt;

&lt;p&gt;You won't be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Consider it a 2 months worth of premium education for free. It's like Netflix, only for life-long learners :-)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodewithjan.com%2Fimages%2Fposts%2Ftwdc-skillshare-cover-play.png" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Installing XAMPP
&lt;/h2&gt;

&lt;p&gt;In order to work with the SQL-based database, you need to install DBMS (Database Management System), but there's an easier way as you can get it as a part of the development stack. Running DMBS is a matter of one click in such a case.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will use XAMPP which is available for both Mac and Windows platforms and it's very easy to install and use, unlike Docker or Vagrant, which are more complicated options.&lt;/p&gt;

&lt;p&gt;Go ahead and download the right installer for your operating system from &lt;a href="https://www.apachefriends.org/download.html" rel="noopener noreferrer"&gt;https://www.apachefriends.org/download.html&lt;/a&gt;. The installation itself is pretty straightforward. Once you run XAMPP, you should be presented with a Control Panel which looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zzfill2zbh35ccsj0bn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zzfill2zbh35ccsj0bn.png" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Terminal and MySQL CLI
&lt;/h2&gt;

&lt;p&gt;You can't become a real developer without understanding the Terminal. It's like an invitation to a special club for professionals and it will make you see things differently.&lt;/p&gt;

&lt;p&gt;These days, people rarely encounter Terminal because everything is hidden under the hood thanks to modern operating systems with GUI (Graphics User Interface).&lt;/p&gt;

&lt;p&gt;But some of you might probably remember MS-DOS, a command-line-based operating system from Microsoft.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feaxhp2ro066270jy6a3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feaxhp2ro066270jy6a3x.png" width="722" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're on Mac, you'll find Terminal as an app from the Spotlight (CMD+SPACE). Once you'll hit Enter, you'll get this window:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyikbov8m2g77thoyo2n3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyikbov8m2g77thoyo2n3.png" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're on Windows, you can use Command Prompt by clicking the search icon next to the Start icon, type &lt;strong&gt;cmd&lt;/strong&gt; and hit Enter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94smefgz51r1r7fwmnb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94smefgz51r1r7fwmnb3.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MySQL CLI is a Command Line Interface for managing MySQL databases. You'll use it inside the Terminal or Command Prompt and it will look similar to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fud0wq30pv1wvayx2ntey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fud0wq30pv1wvayx2ntey.png" width="722" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take a look at how to use it with our XAMPP development environment. To access MySQL with XAMPP, we first need to start the service.&lt;/p&gt;

&lt;p&gt;On both Mac and Windows, you need to go to XAMPP Control Center and start MySQL Database server:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxizd2eat66xhz76pmoxr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxizd2eat66xhz76pmoxr.png" width="676" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wida1vz9o6kj07v9wpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wida1vz9o6kj07v9wpe.png" width="671" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you need to navigate to the appropriate directory to use MySQL CLI.&lt;/p&gt;

&lt;p&gt;On Mac, type these commands in Terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /Appplications/XAMPP/xamppfiles/bin

./mysql -uroot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't need to type any password since the default configuration of XAMPP has no password set for the root user.&lt;/p&gt;

&lt;p&gt;Don't worry if you see &lt;strong&gt;MariaDB&lt;/strong&gt; instead of &lt;strong&gt;mysql&lt;/strong&gt; at the prompt. They both work exactly the same way as far as we are concerned in this tutorial.&lt;/p&gt;

&lt;p&gt;On Windows, type these commands in Command Prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd C:\xampp\mysql\bin

mysql.exe -uroot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Inspecting databases
&lt;/h2&gt;

&lt;p&gt;Now that you know how to connect to MySQL let's start communicating with our database system.&lt;/p&gt;

&lt;p&gt;First, we will take a look at all databases available by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW DATABASES;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6o4xztzbbbxg0sjtotm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6o4xztzbbbxg0sjtotm.png" width="577" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should always see the &lt;strong&gt;information_schema&lt;/strong&gt; database which stores information about all the other databases that our MySQL server maintains. It's also known as system catalog because you can find there everything about regular databases, like their names, access privileges, types and so on.&lt;/p&gt;

&lt;p&gt;To work with a specific database, we need to select it first with the &lt;strong&gt;USE&lt;/strong&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USE INFORMATION_SCHEMA;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foahpgslwljpyb286fwku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foahpgslwljpyb286fwku.png" width="577" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we can inspect the database content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW TABLES;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vedhy90bv3w98s3yy8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vedhy90bv3w98s3yy8r.png" width="578" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take a look at the structure of the CHARACTER_SETS table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW COLUMNS FROM CHARACTER_SETS;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy83gvpux4xzdvd2bute0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy83gvpux4xzdvd2bute0.png" width="576" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you ask MySQL to show you the columns of the table, they will be listed in rows. This might be confusing at first, but it's actually quite logical. Lists are usually just a bunch of rows, and a list of columns is still the list, hence the rows.&lt;/p&gt;

&lt;p&gt;Let's take a look at all records (list of rows) stored in this table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM CHARACTER_SETS;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhi0wqjohwkw9mij1yp18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhi0wqjohwkw9mij1yp18.png" width="718" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, you can see a bunch of rows (records) and four columns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a new database
&lt;/h2&gt;

&lt;p&gt;Let's create a new database for our movies table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE DATABASE talker_db;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxh9v4d0dynge5eb6jv2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxh9v4d0dynge5eb6jv2a.png" width="717" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're adventurous, use your own name for this new database. I picked talker_db because that's the name of the database I'm using in &lt;a href="https://www.twdc.online" rel="noopener noreferrer"&gt;Total Web Development Course&lt;/a&gt; where I teach total beginners how to create their own Facebook-like social platform with private messaging and groups. If you think you might be interested in such skills, check &lt;a href="https://www.twdc.online" rel="noopener noreferrer"&gt;www.twdc.online&lt;/a&gt; for more information about the Total Web Development Course.&lt;/p&gt;

&lt;p&gt;Ok, back to our database. Let's check that it was created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW DATABASES;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11ooj0nm62dboqo4a8ss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11ooj0nm62dboqo4a8ss.png" width="715" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And sure enough! It appeared on the list.&lt;/p&gt;

&lt;p&gt;When you create a database, it's always empty, but we will learn soon how to add some tables.&lt;/p&gt;

&lt;p&gt;To delete the database, you'll use this formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DROP DATABASE talker_db;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But be very careful, because once you hit the Enter, there's no way to retrieve your data. It will be lost forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a new table
&lt;/h2&gt;

&lt;p&gt;We will create a movies table that will hold information about popular Holywood movies. For each movie, we will store its unique identifier, title, length in minutes, genre and main actor.&lt;/p&gt;

&lt;p&gt;This is what we want to achieve:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F574ilp9m8e1709jgzxb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F574ilp9m8e1709jgzxb9.png" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that we need to define five columns with appropriate data types.&lt;/p&gt;

&lt;p&gt;When choosing the right data type for the column, it's important to consider the optimum storage size, because each data type reserves a certain amount of memory. It's a good practice to choose data type which can store all possible values and uses the least amount of memory.&lt;/p&gt;

&lt;p&gt;First, we need to switch to our talker_db database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USE talker_db;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltk1b668v1kuoxhoii9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fltk1b668v1kuoxhoii9k.png" width="718" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we'll create a table by defining its name and columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE movies
(
id int,
title varchar(50),
length tinyint UNSIGNED,
genre varchar(15),
actor varchar(30)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a good practice to divide long and complicated formulas to individual lines with Enter. Remember that until you write semicolon, you can hit Enter and start typing on the new line. Once you write semicolon and hit Enter, the formula will be executed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqor0upnnhv7huxqqd3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqor0upnnhv7huxqqd3b.png" width="722" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, MySQL will give you simple feedback: &lt;strong&gt;Query OK, 0 rows affected&lt;/strong&gt;, and the time it took it to create the table.&lt;/p&gt;

&lt;p&gt;If anything goes wrong or if you omit some column, you can always start again from scratch.&lt;/p&gt;

&lt;p&gt;First, delete the table with this formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DROP TABLES movies;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create it again.&lt;/p&gt;

&lt;p&gt;Let's inspect the structure of the movies table now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW COLUMNS FROM movies;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c7vjrpvizueqoqmobae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c7vjrpvizueqoqmobae.png" width="720" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything looks good and you might have noticed that MySQL automatically added the maximum length for the &lt;strong&gt;int&lt;/strong&gt; and &lt;strong&gt;tinyint&lt;/strong&gt; data types where we didn't define them explicitly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gy9ix4idqdh31ttcv9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gy9ix4idqdh31ttcv9j.png" width="719" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, there are other columns we didn't define at all, Null, Key, Default and Extra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Null&lt;/strong&gt; specifies whether the column can be empty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key&lt;/strong&gt; specifies whether the column is indexed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Default&lt;/strong&gt; specifies the default value in case no value is explicitly specified when a new record is created. NULL means that the value must be always specified while creating a new record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extra&lt;/strong&gt; specifies any additional information, like ordering, timestamp, and so on.&lt;/p&gt;

&lt;p&gt;Let's add a primary key now by modifying the table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE movies ADD PRIMARY KEY(id);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let's display the columns again to see the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW COLUMNS FROM movies;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fba5w2qjcrx663w9vnncs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fba5w2qjcrx663w9vnncs.png" width="722" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the Null property is no longer YES for the id column, because the primary key can't be empty. And the KEY property has PRI value which stands for PRIMARY.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9v6xph7fc78ad4jlhmpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9v6xph7fc78ad4jlhmpt.png" width="722" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's add the automatic incrementation for the id column so we don't have to increment its value manually for each new record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER TABLE movies MODIFY id INT AUTO_INCREMENT;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, let's check the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW COLUMNS FROM movies;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixxps2s7ukc2thpivg65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixxps2s7ukc2thpivg65.png" width="723" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And sure enough, the auto_increment was added to the Extra property of the id column.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inserting table data
&lt;/h2&gt;

&lt;p&gt;Our movies table is still empty as you can check with this formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM movies;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's change that by adding some records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO movies (title, length, genre, actor) VALUES ('The Terminator', 107, 'Sci-Fi', 'Arnold Schwarzenegger');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you always use single quotation marks when inserting string values. Double quotes can have different meaning based on SQL modes.&lt;/p&gt;

&lt;p&gt;Also notice, that we didn't specify any value for the id column as it will be added and incremented automatically.&lt;/p&gt;

&lt;p&gt;Let's check our first record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM movies;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzg0zul1zfu2v5meaft04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzg0zul1zfu2v5meaft04.png" width="721" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! Let's add some more records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO movies (title, length, genre, actor) VALUES ('Die Hard', 131, 'Thriller', 'Bruce Willis');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO movies (title, length, genre, actor) VALUES ('First Blood', 93, 'Action', 'Sylvester Stallone');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let's check the content of the table now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM movies;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5mnzbansblx13zzq0xb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5mnzbansblx13zzq0xb.png" width="722" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, MySQL is happily adding a new id every time you add a new record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to learn more?
&lt;/h2&gt;

&lt;p&gt;The rest of this tutorial is available for free on &lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt; as a part of the much larger and far more detailed video course. Again, if you’re new to Skillshare, you’ll also get premium access to more than 22.000 courses. Remember, that you won’t be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Skillshare is Netflix for life-long learners.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodewithjan.com%2Fimages%2Fposts%2Ftwdc-skillshare-cover-play.png" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

</description>
      <category>sql</category>
      <category>mysql</category>
    </item>
    <item>
      <title>SwiftUI By Examples</title>
      <dc:creator>HZ</dc:creator>
      <pubDate>Thu, 09 Jan 2020 17:35:22 +0000</pubDate>
      <link>https://dev.to/zavrelj/swiftui-by-examples-5ag7</link>
      <guid>https://dev.to/zavrelj/swiftui-by-examples-5ag7</guid>
      <description>&lt;p&gt;In this tutorial, I will teach you about SwiftUI, Apple's latest addition to the amazing world of iOS development. SwiftUI is nothing short of a game-changer. It makes the life of iOS developer so much easier and I can't wait to show you all the magic waiting for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;We will go together through the entire process of building the SwiftUI app starting from scratch. We will create a list of fancy conference rooms based on the SwiftUI Introductory session which Apple held in WWDC 2019. Sadly, the code they used in that session is already outdated, so if you follow that video, you'll get stuck pretty quickly. &lt;/p&gt;

&lt;p&gt;That's why I have decided to take this tutorial as an opportunity to create an up-to-date version of the app using the latest SwiftUI API. I also wanted to make it more streamlined and easier to understand, even for total beginners.&lt;/p&gt;

&lt;h2&gt;
  
  
  A shameless plug
&lt;/h2&gt;

&lt;p&gt;If you prefer a video-course version of this text-based tutorial, go get it for free on &lt;a href="https://link.zavrel.net/swiftui-course" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt;. If you're new to Skillshare, you'll get premium access not only to this course for free, but to &lt;strong&gt;all of my other courses as well and for a good measure, to over 22.000 courses&lt;/strong&gt; currently hosted on Skillshare. You won't be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Consider it a 2 months worth of premium education for free. It's like Netflix, only for life-long learners :-)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://link.zavrel.net/swiftui-course" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnek7tlebfjfl6folf5c.png" width="554" height="320"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;p&gt;To make the most of this tutorial and to be able to truly follow me step by step, you'll need macOS Catalina or newer and Xcode 11 or newer.  With Xcode 11, Apple introduced live previews so you no longer need to compile your project and run it in the Simulator each time you make some changes to your code. However, this feature won't work on older macOS. So even though, you can install Xcode 11 on older macOS, you won't get live previews unless you run Catalina.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55maar3rdk1fpmjkpye3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55maar3rdk1fpmjkpye3.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414228/codewithjan/swiftui-by-examples/swiftui-by-examples-02.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;To save your time I prepared some resources so you don't have to manually type everything from scratch. You'll find everything I'm referring to in the video in a package called &lt;strong&gt;swiftui-rooms-resources.zip&lt;/strong&gt; file which you should download and unpack to the location of your choice.&lt;/p&gt;

&lt;p&gt;You'll find it at &lt;a href="http://www.zavrel.net/swiftui-rooms-resources" rel="noopener noreferrer"&gt;www.zavrel.net/swiftui-rooms-resources&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a basic structure
&lt;/h2&gt;

&lt;p&gt;Run Xcode, create a new &lt;strong&gt;Single View App&lt;/strong&gt; project and call it &lt;strong&gt;Rooms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnijap40yrc06d2nej1dt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnijap40yrc06d2nej1dt.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414336/codewithjan/swiftui-by-examples/swiftui-by-examples-03.png" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before you hit the &lt;strong&gt;Next&lt;/strong&gt; button, make sure that the &lt;strong&gt;Language&lt;/strong&gt; is set to &lt;strong&gt;Swift&lt;/strong&gt; and the &lt;strong&gt;User Interface&lt;/strong&gt; to &lt;strong&gt;SwiftUI&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;In the next window, check the &lt;strong&gt;Source Control&lt;/strong&gt; and save the project to the Desktop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2i7hv5q1sawso1ygjn1s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2i7hv5q1sawso1ygjn1s.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414414/codewithjan/swiftui-by-examples/swiftui-by-examples-04.png" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what your Xcode project should look like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ilcde4rydwju3pmglwq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ilcde4rydwju3pmglwq.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414466/codewithjan/swiftui-by-examples/swiftui-by-examples-05.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you're at least somewhat familiar with Xcode. If you're on Catalina, you'll notice the column with automatic preview and you'll probably see the message that "Automatic preview updating paused". When you click the tiny icon next to the message, you'll get the explanation that this is perfectly normal when you make such changes that Xcode has to rebuild your project.&lt;/p&gt;

&lt;p&gt;Go ahead and click the &lt;strong&gt;Resume&lt;/strong&gt; button to the right:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38fx9nkuv8o1umdsw0hr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38fx9nkuv8o1umdsw0hr.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414534/codewithjan/swiftui-by-examples/swiftui-by-examples-06.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see the preview of the iPhone with a simple welcome message at the center of the screen. To have more real estate for coding, let's hide the right pane by click this icon:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy7z0hptaucbgfa8vagf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy7z0hptaucbgfa8vagf.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414595/codewithjan/swiftui-by-examples/swiftui-by-examples-07.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've been working with Storyboards in the past, this might look familiar. However, with Storyboards, you had to choose whether you wanted to code manually or to build your app with visual items. You couldn't use both at the same time. With SwiftUI, you can go back and forth as you'll soon see.&lt;/p&gt;

&lt;p&gt;Go ahead and click the &lt;strong&gt;Hello World&lt;/strong&gt; text on the canvas. It will be automatically selected in the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpfnbhbadqv57sk5eyoa4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpfnbhbadqv57sk5eyoa4.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414646/codewithjan/swiftui-by-examples/swiftui-by-examples-08.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, change the text in the code to &lt;strong&gt;Rooms&lt;/strong&gt; and see what happens in the canvas:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyv383f9nt7cc33xfljk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyv383f9nt7cc33xfljk.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414689/codewithjan/swiftui-by-examples/swiftui-by-examples-09.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's right. The code and the canvas work together seamlessly!&lt;/p&gt;

&lt;p&gt;Since we want to create a list of rooms, let's add some more details about them. This time, though, we will take a visual approach and add a new text by just dragging it out onto the canvas.&lt;/p&gt;

&lt;p&gt;Click the big + button at the top and drag the Text view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzn4edtt3dvs3kqpsmrnm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzn4edtt3dvs3kqpsmrnm.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414738/codewithjan/swiftui-by-examples/swiftui-by-examples-10.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not only Xcode added the text onto the canvas, but as you can see, it also updated the code and automatically added a vertical stack so both text views are nicely stacked under each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75k5rw201d3il1im5vvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F75k5rw201d3il1im5vvs.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578414788/codewithjan/swiftui-by-examples/swiftui-by-examples-11.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Source Control
&lt;/h2&gt;

&lt;p&gt;Now I hope you've noticed that blue ribbon in the code gutter. This is Git tracking the changes. Click the ribbon and select &lt;strong&gt;Show Change&lt;/strong&gt; from the popup menu to reveal what changes you have made to the code so far. The gray section shows the deleted code and the blue section shows added code. Now, you can commit these changes. This will save this state of the project so you can later return to it if anything goes wrong.&lt;/p&gt;

&lt;p&gt;Select Source Control → Commit... from the top menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t8lhxiqxflheyk0hsms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t8lhxiqxflheyk0hsms.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578416258/codewithjan/swiftui-by-examples/swiftui-by-examples-12.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll be presented with a new window where you can select the &lt;strong&gt;ContentView.swift&lt;/strong&gt; file to see the changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbf4qw8njdknb0ksg6ju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbf4qw8njdknb0ksg6ju.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578416315/codewithjan/swiftui-by-examples/swiftui-by-examples-13.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type the commit message at the bottom of the window and click the &lt;strong&gt;Commit 1 File&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkktymvoqeywt536mehhs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkktymvoqeywt536mehhs.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578416365/codewithjan/swiftui-by-examples/swiftui-by-examples-14.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you do that, the blue ribbon will disappear, but you can always check the history to see your commits by clicking the &lt;strong&gt;Source Control navigator&lt;/strong&gt; in the top menu, selecting the master branch, and double-clicking the commit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftf8kkx88fdnb9uy9fh21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftf8kkx88fdnb9uy9fh21.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578416415/codewithjan/swiftui-by-examples/swiftui-by-examples-15.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool! Now that you know how to commit the changes to your code and build the history, I suggest you do it often, or at least whenever you make some big changes to your code which could potentially lead to disaster. It's better to be safe than sorry and rewrite your work from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine-tuning the layout
&lt;/h2&gt;

&lt;p&gt;Ok, let's go back to our code and replace the Placeholder text with the number of people our room can hold. We will initially use the hard-coded value:&lt;/p&gt;

&lt;pre&gt;
struct ContentView: View {
    var body: some View {
        VStack {
            Text("Rooms")
            Text("20 people")
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;As you'd expect. Two things will happen immediately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There's a new blue ribbon as we made a change to our code since the last commit&lt;/li&gt;
&lt;li&gt;Canvas has been updated with a new text&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgtlsxeyahj8q5z4w0du.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgtlsxeyahj8q5z4w0du.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422164/codewithjan/swiftui-by-examples/swiftui-by-examples-16.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's add some image to our room now. With SwiftUI, it's very easy to add a column by using HStack which stands for a horizontal stack. This way, we can define a new column for the image.&lt;/p&gt;

&lt;p&gt;Let's wrap our VStack in the HStack to achieve this.&lt;/p&gt;

&lt;p&gt;CMD-Click the VStack and select &lt;strong&gt;Embed in HStack&lt;/strong&gt; from the menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqhlkk81yyaziex67181j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqhlkk81yyaziex67181j.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422230/codewithjan/swiftui-by-examples/swiftui-by-examples-17.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can easily add the image in front of the room description and it will end up in the left column:&lt;/p&gt;

&lt;pre&gt;
struct ContentView: View {
    var body: some View {
        HStack {
            Image(systemName: "photo")
            VStack {
                Text("Rooms")
                Text("20 people")
            }
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;This is the placeholder using the SF Symbols which is a set of over 1,500 highly configurable symbols that you can use in your iOS 13, watchOS 6 and tvOS 13 apps. They come in nine weights and three scales, small, medium and large.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5mdz0qpjclxe81a84eq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5mdz0qpjclxe81a84eq.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422284/codewithjan/swiftui-by-examples/swiftui-by-examples-18.png" width="735" height="265"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's change some properties of our VStack now.&lt;/p&gt;

&lt;p&gt;In canvas, CMD+Click the VStack and choose &lt;strong&gt;Show SwiftUI Inspector...&lt;/strong&gt; from the popup menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl28dml2qkqiets2gg2vo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl28dml2qkqiets2gg2vo.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422343/codewithjan/swiftui-by-examples/swiftui-by-examples-19.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the Alignment to leading and watch how the code will change:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbp91rmmbf5iepio2wzfz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbp91rmmbf5iepio2wzfz.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422398/codewithjan/swiftui-by-examples/swiftui-by-examples-20.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's change the text size of the second Text view and set it to subheadline.&lt;/p&gt;

&lt;p&gt;CMD+Click that text in canvas, choose &lt;strong&gt;Show SwiftUI Inspector...&lt;/strong&gt; again and change the &lt;strong&gt;Font&lt;/strong&gt; to &lt;strong&gt;Subheadline&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1pl3px7s5d42ggr1kc48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1pl3px7s5d42ggr1kc48.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422456/codewithjan/swiftui-by-examples/swiftui-by-examples-21.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your code should look like this now:&lt;/p&gt;

&lt;pre&gt;
struct ContentView: View {
    var body: some View {
        HStack {
            Image(systemName: "photo")
            VStack(alignment: .leading) {
                Text("Rooms")
                Text("20 people")
                    .font(.subheadline)
            }
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;The &lt;strong&gt;.font(.subheadline)&lt;/strong&gt; is the so-called modifier. It's a kind of SwiftUI method that customizes how the specific View looks and behaves.&lt;/p&gt;

&lt;p&gt;Let's add another modifier in the code now to change the color of the second Text View to be a secondary color:&lt;/p&gt;

&lt;pre&gt;
VStack(alignment: .leading) {
    Text("Rooms")
    Text("20 people")
        .font(.subheadline)
        .foregroundColor(.secondary)
}
&lt;/pre&gt;

&lt;p&gt;Again, this change will immediately update the canvas where the text is gray now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ue20zgtyqo1rgz28x35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ue20zgtyqo1rgz28x35.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422522/codewithjan/swiftui-by-examples/swiftui-by-examples-22.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a list of items
&lt;/h2&gt;

&lt;p&gt;Ok, now it's time to create a list of rooms and you'll see how ridiculously easy it is in SwiftUI. CMD-Click the HStack and select &lt;strong&gt;Embed in List&lt;/strong&gt; from the popup menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feg6xaw6o9th5qzuwuv02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feg6xaw6o9th5qzuwuv02.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422588/codewithjan/swiftui-by-examples/swiftui-by-examples-23.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, Xcode will create a list of five items. And, as you might expect, that code changes will be rendered in the canvas as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3jivmspwdr0cj0fg6z9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3jivmspwdr0cj0fg6z9.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422654/codewithjan/swiftui-by-examples/swiftui-by-examples-24.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, now it's time to add some external data, so open the &lt;strong&gt;swift-room-resources&lt;/strong&gt; folder and drag everything to Xcode's Project navigator in the left pane:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14cp2ic3pkrvv4bwqag4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14cp2ic3pkrvv4bwqag4.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422710/codewithjan/swiftui-by-examples/swiftui-by-examples-25.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take a look at the &lt;strong&gt;Room.swift&lt;/strong&gt; file.&lt;/p&gt;

&lt;pre&gt;
import SwiftUI

struct Room: Identifiable {
    var id = UUID()
    var name: String
    var building: String
    var floor: String
    var capacity: Int
    var hasVideo: Bool = false
    var imageName: String
}

let testData = [
    Room(name: "Tree Room", building: "A", floor: "1", capacity: 6, hasVideo: true, imageName: "room01"),
    Room(name: "Lamp &amp;amp; Pillows", building: "B", floor: "1", capacity: 8, hasVideo: false, imageName: "room02"),
    Room(name: "Red Looks Great", building: "D", floor: "2", capacity: 16, hasVideo: true, imageName: "room03"),
    Room(name: "Bug On The Wall", building: "A", floor: "3", capacity: 10, hasVideo: true, imageName: "room04"),
    Room(name: "Candles", building: "C", floor: "3", capacity: 12, hasVideo: false, imageName: "room05"),
    Room(name: "Queen Size", building: "F", floor: "1", capacity: 8, hasVideo: false, imageName: "room06"),
    Room(name: "Small But Sweet", building: "E", floor: "1", capacity: 10, hasVideo: true, imageName: "room07"),
    Room(name: "Modern Screen", building: "B", floor: "4", capacity: 7, hasVideo: false, imageName: "room08"),
    Room(name: "Yellow Matrix", building: "D", floor: "3", capacity: 1, hasVideo: false, imageName: "room09")
]
&lt;/pre&gt;

&lt;p&gt;It contains the Room struct which has seven properties that define the id and the name of the room as well as where the room is located, what's its capacity, whether it has video conferencing setup and finally, even the photo of the room.&lt;/p&gt;

&lt;p&gt;Our struct conforms to Identifiable protocol which means that it can be identified uniquely. Also, there must be a property called id that contains a unique identifier. As you can see, we covered that as well by defining the id property as UUID() which stands for a &lt;strong&gt;universally unique identifier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;UUIDs are long hexadecimal strings such as 08B15DB4-2F02-4AB8-A965-67A9C90D8A44. If we generated 1 UUID every second for a billion years, we might begin to have the slightest chance of generating a duplicate, so I guess this is indeed a pretty unique identifier.&lt;/p&gt;

&lt;p&gt;Below the struct definition, we have an array of nine instances of Room struct, each holding its own set of properties with specific values. We will use these test data for debugging our app.&lt;/p&gt;

&lt;p&gt;Now, to pass our test data from the Room.swift file, we need to add a new property to our ContentView struct. So back in the &lt;strong&gt;ContentView.swift&lt;/strong&gt; file, add a new variable  named &lt;strong&gt;rooms&lt;/strong&gt; which is an array of instances of the Room struct:&lt;/p&gt;

&lt;pre&gt;
struct ContentView: View {
    var rooms: [Room] = []
    
    var body: some View {
        List(0 ..&amp;lt; 5) { item in
            Image(systemName: "photo")
            VStack(alignment: .leading) {
                Text("Rooms")
                Text("20 people")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Next, we'll pass our test data to the preview:&lt;/p&gt;

&lt;pre&gt;
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(rooms: testData)
    }
}
&lt;/pre&gt;

&lt;p&gt;This means that we just use the testData array defined in the &lt;strong&gt;Rooms.swift&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;Now we can finally use our test data in the list instead of hardcoded values.&lt;/p&gt;

&lt;p&gt;So we will list the items of the rooms array while each item will be referred to as room. This way, we can access the properties of an individual item, like a name:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
        List(rooms) { room in
            Image(systemName: "photo")
            VStack(alignment: .leading) {
                Text(room.name)
                Text("20 people")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
}
&lt;/pre&gt;

&lt;p&gt;And sure enough! Our list of rooms is immediately updated in the preview so it shows all nine items and their names coming from the testData array.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotwwn6kdkvepg8hfxnzd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotwwn6kdkvepg8hfxnzd.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422778/codewithjan/swiftui-by-examples/swiftui-by-examples-26.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll just commit the changes because this is a huge step forward!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29l990gtffdx7eoyfk00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29l990gtffdx7eoyfk00.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578422840/codewithjan/swiftui-by-examples/swiftui-by-examples-27.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding more details about rooms
&lt;/h2&gt;

&lt;p&gt;Ok, let's use another information from our test data, which is the capacity of people for each room:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    List(rooms) { room in
        Image(systemName: "photo")
        VStack(alignment: .leading) {
            Text(room.name)
            Text("&lt;del&gt;20&lt;/del&gt;\(room.capacity) people")
                .font(.subheadline)
                .foregroundColor(.secondary)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Putting the room.capacity property inside the backslash and parentheses &lt;strong&gt;(room.capacity)&lt;/strong&gt; allows us to directly transfer the Int type to String type. This is a so-called string interpolation which is just a fancy name for combining variables and constants inside the string.&lt;/p&gt;

&lt;p&gt;Finally, we can use real images for our rooms instead of the system placeholders:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    List(rooms) { room in
        Image(&lt;del&gt;systemName: "photo"&lt;/del&gt;room.imageName)
        VStack(alignment: .leading) {
            Text(room.name)
            Text("\(room.capacity) people")
                .font(.subheadline)
                .foregroundColor(.secondary)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Ok, but this is not what we wanted to achieve here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpd353kmirx44o3zjgruf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpd353kmirx44o3zjgruf.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578423245/codewithjan/swiftui-by-examples/swiftui-by-examples-28.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem is that Xcode will use the original image size by default and since our room photos are 1024x1024 pixels, one image will easily use the whole screen and some more.&lt;/p&gt;

&lt;p&gt;But, there's an easy fix.&lt;/p&gt;

&lt;p&gt;First, we need to tell SwiftUI that are images are resizeable, next, we will change their size to 50x50 pixels:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    List(rooms) { room in
        Image(room.imageName)
            .resizable()
            .frame(width: 50.0, height: 50.0)
        VStack(alignment: .leading) {
            Text(room.name)
            Text("\(room.capacity) people")
                .font(.subheadline)
                .foregroundColor(.secondary)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Now it looks much better, but I want to add one final touch. Let's make the corners rounded:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    List(rooms) { room in
        Image(room.imageName)
            .resizable()
            .frame(width: 50.0, height: 50.0)
            .cornerRadius(10)
        VStack(alignment: .leading) {
            Text(room.name)
            Text("\(room.capacity) people")
                .font(.subheadline)
                .foregroundColor(.secondary)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Now, it's absolutely perfect!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4pic1ue1rab9q42i7uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4pic1ue1rab9q42i7uq.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578423329/codewithjan/swiftui-by-examples/swiftui-by-examples-29.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't forget to commit the changes so we can move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding navigation links
&lt;/h2&gt;

&lt;p&gt;Now we want to be able to tap our individual list items to see more details about each room.&lt;/p&gt;

&lt;p&gt;First, we need to wrap our list in the NavigationView:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List(rooms) { room in
            Image(room.imageName)
                .resizable()
                .frame(width: 50.0, height: 50.0)
                .cornerRadius(10)
            VStack(alignment: .leading) {
                Text(room.name)
                Text("\(room.capacity) people")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Next, we will add the title for the navigation bar:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List(rooms) { room in
            Image(room.imageName)
                .resizable()
                .frame(width: 50.0, height: 50.0)
                .cornerRadius(10)
            VStack(alignment: .leading) {
                Text(room.name)
                Text("\(room.capacity) people")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
        .navigationBarTitle("Rooms")
    }
}
&lt;/pre&gt;

&lt;p&gt;In order to be able to make our items clickable, or rather tappable, we need to transform them into buttons. This is pretty easy. We'll just wrap the items in &lt;strong&gt;NavigationLink&lt;/strong&gt; which takes a destination which will be our detail view, but we'll start with a simple text for now and display just the room name:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List(rooms) { room in
            NavigationLink(destination: Text(room.name)) {
                Image(room.imageName)
                    .resizable()
                    .frame(width: 50.0, height: 50.0)
                    .cornerRadius(10)
                VStack(alignment: .leading) {
                    Text(room.name)
                    Text("\(room.capacity) people")
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                }
            }
        }
    .navigationBarTitle("Rooms")
    }
}
&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdipjnc60in2ieox03nvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdipjnc60in2ieox03nvc.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578423432/codewithjan/swiftui-by-examples/swiftui-by-examples-30.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how SwiftUI automatically added an arrow next to each item that indicates that there's more content available.&lt;/p&gt;

&lt;p&gt;Now, click the play button to test the behavior of the app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq96yu4fso84xta7z5oe3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq96yu4fso84xta7z5oe3.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578423525/codewithjan/swiftui-by-examples/swiftui-by-examples-31.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll get to the live mode, where you can interact with the preview. You should be able to tap each item and see the name of the room in the middle of the screen. You can also go back by tapping the &lt;strong&gt;&amp;lt; Rooms&lt;/strong&gt; link at the top-left corner. Alternatively, you can also swipe from the left edge as you'll familiar with from your iPhone, and everything works as expected without writing any additional code. That's the power of SwiftUI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82iy2bnu4gmpweske88l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82iy2bnu4gmpweske88l.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578423840/codewithjan/swiftui-by-examples/swiftui-by-examples-32.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you're done, hit the blue stop button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ff9v6q4bs02xmxc5evj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ff9v6q4bs02xmxc5evj.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578423962/codewithjan/swiftui-by-examples/swiftui-by-examples-33.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great job! Let's commit our changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extracting Subview
&lt;/h2&gt;

&lt;p&gt;Now I'll show you how to make the code more organized and readable by extracting subviews.&lt;/p&gt;

&lt;p&gt;CMD-Click the NavigationLink view and choose &lt;strong&gt;Extract Subview&lt;/strong&gt; from the popup-menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx2zq7d3cu1oj83fzr1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx2zq7d3cu1oj83fzr1l.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578453215/codewithjan/swiftui-by-examples/swiftui-by-examples-34.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Xcode will create a new struct with the extracted content and we'll link it back by choosing the name for our new View which will be &lt;strong&gt;RoomCell&lt;/strong&gt; in our case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp7mt37filq05dznpayqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp7mt37filq05dznpayqi.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578453297/codewithjan/swiftui-by-examples/swiftui-by-examples-35.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, we have an unresolved identifier "room" which means that we didn't pass the room instance to this subview yet.&lt;/p&gt;

&lt;p&gt;To fix this, we need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create the room constant of the Room type in our subview&lt;/li&gt;
&lt;li&gt;pass the room parameter when calling our newly created RoomCell subview&lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;
struct RoomCell: View {
    let room: Room
    
    var body: some View {
        NavigationLink(destination: Text(room.name)) {
            Image(room.imageName)
                .resizable()
                .frame(width: 50.0, height: 50.0)
                .cornerRadius(10)
            VStack(alignment: .leading) {
                Text(room.name)
                Text("\(room.capacity) people")
                    .font(.subheadline)
                    .foregroundColor(.secondary)
            }
        }
    }
}
&lt;/pre&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List(rooms) { room in
            RoomCell(room: room)
        }
    .navigationBarTitle("Rooms")
    }
}
&lt;/pre&gt;

&lt;p&gt;We didn't add any new functionality with this modification, but we made our code more readable because the body now contains just the list of room instances that are defined in the RoomCell struct.&lt;/p&gt;

&lt;p&gt;You can test in the live mode that everything works exactly as before we changed this.&lt;/p&gt;

&lt;p&gt;When you're done, commit the changes so we can move on. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a detail view
&lt;/h2&gt;

&lt;p&gt;Ok, let's build our detail view now.&lt;/p&gt;

&lt;p&gt;We'll create a new file with the SwiftUI template, so go to File → New → File... (or click CMD+N) and select &lt;strong&gt;SwiftUI View&lt;/strong&gt; in iOS tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00do9xea2bsf61ex5ov9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00do9xea2bsf61ex5ov9.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578453599/codewithjan/swiftui-by-examples/swiftui-by-examples-36.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's save it as RoomDetail.&lt;/p&gt;

&lt;p&gt;As you can see, Xcode created a new View struct and preview code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrka542m6jupfhgv3cj4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrka542m6jupfhgv3cj4.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578453639/codewithjan/swiftui-by-examples/swiftui-by-examples-37.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, to see the content of our test data list of rooms, we need to add a new property to our RoomDetail struct and pass it to the preview as well.&lt;/p&gt;

&lt;p&gt;First, create a new constant named room which is a type of Room:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail: View {
    let room: Room
    
    var body: some View {
        Text("Hello, World!")
    }
}
&lt;/pre&gt;

&lt;p&gt;Next, let's call the RoomDetail struct to create an instance with values loaded from the first item in our testData array:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail_Previews: PreviewProvider {
    static var previews: some View {
        RoomDetail(room: testData[0])
    }
}
&lt;/pre&gt;

&lt;p&gt;Finally, we can use that data in the body of our detail view.&lt;/p&gt;

&lt;p&gt;Let's load the image first:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail: View {
    let room: Room
    
    var body: some View {
        &lt;del&gt;Text("Hello, World!")&lt;/del&gt;
        Image(room.imageName)
    }
}
&lt;/pre&gt;

&lt;p&gt;As you can see, this will load the image of the first room from our list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ti8zy1qbqb6drqgcfrq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ti8zy1qbqb6drqgcfrq.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578453688/codewithjan/swiftui-by-examples/swiftui-by-examples-38.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But unfortunately, it's too large for our view, because, as you already know, SwiftUI shows all images at their original size by default.&lt;/p&gt;

&lt;p&gt;Luckily, there's an easy fix we already used. So let's add the &lt;strong&gt;.resizable()&lt;/strong&gt; modifier to our image, and to maintain its aspect ratio, we'll add another modifier, the .aspectRatio with contentMode set to &lt;strong&gt;.fit&lt;/strong&gt;:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail: View {
    let room: Room
    
    var body: some View {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: .fit)
    }
}
&lt;/pre&gt;

&lt;p&gt;Great job! Let's commit the changes so we can move on.&lt;/p&gt;

&lt;p&gt;Now we need to update the items so they push our newly-created RoomDetail view once tapped.&lt;/p&gt;

&lt;p&gt;So go back to the &lt;strong&gt;ContentView.swift&lt;/strong&gt; file and update the destination of the NavigationLink:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationLink(destination: &lt;del&gt;Text(room.name)&lt;/del&gt;RoomDetail(room: room)) {
        Image(room.imageName)
            .resizable()
            .frame(width: 50.0, height: 50.0)
            .cornerRadius(10)
        VStack(alignment: .leading) {
            Text(room.name)
            Text("\(room.capacity) people")
                .font(.subheadline)
                .foregroundColor(.secondary)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Run the live mode in Preview and tap any item to see the appropriate image of the room:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xgkyjtrajvx9zu5p3y2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9xgkyjtrajvx9zu5p3y2.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578453735/codewithjan/swiftui-by-examples/swiftui-by-examples-39.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, let's add the title of the detail view.&lt;/p&gt;

&lt;p&gt;Go back to the &lt;strong&gt;RoomDetail.swift&lt;/strong&gt; file and add another modifier to the Image:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    Image(room.imageName)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .navigationBarTitle(Text(room.name))
}
&lt;/pre&gt;

&lt;p&gt;To see it in the preview, just wrap RoomDetail in the Navigation View like this:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView { RoomDetail(room: testData[0]) }
    }
}
&lt;/pre&gt;

&lt;p&gt;And sure enough, the room name is finally there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe213qjvk014w4sujcfrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe213qjvk014w4sujcfrv.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578453799/codewithjan/swiftui-by-examples/swiftui-by-examples-40.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One last thing. Let's make the display mode to be inline for the title so it looks more professional:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    Image(room.imageName)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .navigationBarTitle(Text(room.name), displayMode: .inline)
}
&lt;/pre&gt;

&lt;p&gt;Now it's much better:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5g5sdio22m7p98tlnw94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5g5sdio22m7p98tlnw94.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578453851/codewithjan/swiftui-by-examples/swiftui-by-examples-41.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Zooming the image
&lt;/h2&gt;

&lt;p&gt;Now we want to be able to change the contentMode of the aspectRatio from &lt;strong&gt;.fit&lt;/strong&gt; to &lt;strong&gt;.fill&lt;/strong&gt; to be able to see the details of the room image. Yes, we could do it by constantly changing the code like this, but it's out of the question for the regular user so we need to find another way.&lt;/p&gt;

&lt;p&gt;First, we'll add a new property that will automatically keep the state of the image, that is whether it's zoomed or not:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail: View {
    let room: Room
    @State private var zoomed = false
    
    var body: some View {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
    }
}
&lt;/pre&gt;

&lt;p&gt;Next, we'll test whether zoomed is true or false and change the contentMode of the aspectRatio accordingly:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail: View {
    let room: Room
    @State private var zoomed = false
    
    var body: some View {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
    }
}
&lt;/pre&gt;

&lt;p&gt;This is just a shorthand of the if - else code, also known as ternary operator. Basically, you test if the first condition is true (in our case if zoomed is true) and if it is, you'll apply the value behind the question mark, otherwise, you'll apply the value behind the colon.&lt;/p&gt;

&lt;p&gt;You'll probably more familiar with this code, but it has the same meaning:&lt;/p&gt;

&lt;pre&gt;
if zoomed {
    .fill
}else{
    .fit
}
&lt;/pre&gt;

&lt;p&gt;Finally, we need to add a tap gesture which will toggle our zoomed property:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail: View {
    let room: Room
    @State private var zoomed = false
    
    var body: some View {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                self.zoomed.toggle()
            }
    }
}
&lt;/pre&gt;

&lt;p&gt;Now, activate the live mode and tap on the image a few times to see how it nicely zooms in and out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4ylhfvwb2qz3pa9wqjx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4ylhfvwb2qz3pa9wqjx.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578454625/codewithjan/swiftui-by-examples/swiftui-by-examples-42.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One last thing would be to animate the change so it's more subtle. This is very easy, just wrap the toggle command inside the &lt;strong&gt;withAnimation {}&lt;/strong&gt; &lt;/p&gt;

&lt;pre&gt;
.onTapGesture {
    withAnimation { self.zoomed.toggle() }
}
&lt;/pre&gt;

&lt;p&gt;Test the zooming again. It looks much better now!&lt;/p&gt;

&lt;p&gt;Commit the changes so we can move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a video icon
&lt;/h2&gt;

&lt;p&gt;Let's add a video icon to the detail view which will indicate whether the room has video-conferencing capabilities. To add views on top of each other, we use the ZStack, so wrap the Image view inside the ZStack and add a new view below it like this:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail: View {
    let room: Room
    @State private var zoomed = false
    
    var body: some View {
        ZStack {
            Image(room.imageName)
                .resizable()
                .aspectRatio(contentMode: zoomed ? .fill : .fit)
                .navigationBarTitle(Text(room.name), displayMode: .inline)
                .onTapGesture {
                    withAnimation { self.zoomed.toggle() }
                }
            
            Image(systemName: "video.fill")
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;And, as you can see the video icon appear in the middle of the screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnovsd2p9pgj994l4tt3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnovsd2p9pgj994l4tt3i.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578454687/codewithjan/swiftui-by-examples/swiftui-by-examples-43.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But it's too small, so let's make it larger. Since it's actually a font, we can use a &lt;strong&gt;.font&lt;/strong&gt; modifier:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation { self.zoomed.toggle() }
            }
        
        Image(systemName: "video.fill")
            .font(.title)
    }
}
&lt;/pre&gt;

&lt;p&gt;Now, let's place the video icon to the top-left corner of the room image:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation { self.zoomed.toggle() }
            }
        
        Image(systemName: "video.fill")
            .font(.title)
    }
}
&lt;/pre&gt;

&lt;p&gt;And to make it even better-looking, we can add some padding:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation { self.zoomed.toggle() }
            }
        
        Image(systemName: "video.fill")
            .font(.title)
            .padding(.all)
    }
}
&lt;/pre&gt;

&lt;p&gt;Now it looks fantastic!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ew3dil7f6uvfe3fu5e6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ew3dil7f6uvfe3fu5e6.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578454738/codewithjan/swiftui-by-examples/swiftui-by-examples-44.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, as always, before we move on, commit the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a flexible frame
&lt;/h2&gt;

&lt;p&gt;What if we want the video icon to be at the very top-left corner of the screen rather than the room image?&lt;/p&gt;

&lt;p&gt;We can achieve that by adding the flexible frame modifier to the Image view and specify its width and height so it stretches to fill the whole screen while keeping the room image in its place:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation { self.zoomed.toggle() }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        
        Image(systemName: "video.fill")
            .font(.title)
            .padding(.all)
    }
}
&lt;/pre&gt;

&lt;p&gt;Now, we want our video icon to appear only when the room has video-conferencing capabilities.&lt;/p&gt;

&lt;p&gt;So we'll display the video icon only if the &lt;strong&gt;room.hasVideo&lt;/strong&gt; property is true:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation { self.zoomed.toggle() }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        
        if room.hasVideo {
            Image(systemName: "video.fill")
                .font(.title)
                .padding(.all)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;And to check that it's working, we can change the room in our preview data to the one which doesn't have video-conferencing capabilities, so its &lt;strong&gt;hasVideo&lt;/strong&gt; property is false.&lt;/p&gt;

&lt;p&gt;Go to the &lt;strong&gt;Room.swift&lt;/strong&gt; file to see that the second room is one of those. Back to the &lt;strong&gt;RoomDetail.swift&lt;/strong&gt; file, let's change the index of the array from 0 to 1 to show the second room:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView { RoomDetail(room: testData[&lt;del&gt;0&lt;/del&gt;1]) }
    }
}
&lt;/pre&gt;

&lt;p&gt;And sure enough! The video icon is gone!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nop0l6qlzu6zq7at8u6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4nop0l6qlzu6zq7at8u6.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578454810/codewithjan/swiftui-by-examples/swiftui-by-examples-45.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But there's an even better solution. We can have multiple previews, each with a different room. To display multiple previews, we just need to group them, so CMD+Click the NavigationView in the preview section and select the &lt;strong&gt;Group&lt;/strong&gt; option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w8dnx68mx4xa4a9rlri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w8dnx68mx4xa4a9rlri.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578454860/codewithjan/swiftui-by-examples/swiftui-by-examples-46.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can create a second version of the preview using different test data:&lt;/p&gt;

&lt;pre&gt;
struct RoomDetail_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            NavigationView { RoomDetail(room: testData[0]) }
            NavigationView { RoomDetail(room: testData[1]) }
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Amazing! Now we can see both previews at the same time!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5f6nyspnsolxd82ez92.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5f6nyspnsolxd82ez92.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578454913/codewithjan/swiftui-by-examples/swiftui-by-examples-47.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Commit the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Animating video icon
&lt;/h2&gt;

&lt;p&gt;Now we want to improve the video icon behavior so that it disappears when the image is zoomed.&lt;/p&gt;

&lt;p&gt;To do that, we'll just update our condition for showing the video icon like this:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation { self.zoomed.toggle() }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        
        if room.hasVideo &amp;amp;&amp;amp; !zoomed {
            Image(systemName: "video.fill")
                .font(.title)
                .padding(.all)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Go ahead and test it in the live mode. When you zoom the image, the video icon disappears. Perfect!&lt;/p&gt;

&lt;p&gt;Let's make it even better by making the icon slides out and in:&lt;/p&gt;

&lt;pre&gt;
if room.hasVideo &amp;amp;&amp;amp; !zoomed {
      Image(systemName: "video.fill")
          .font(.title)
          .padding(.all)
          .transition(.move(edge: .leading))
}
&lt;/pre&gt;

&lt;p&gt;And let's test it again. Now it's much better!&lt;/p&gt;

&lt;p&gt;Finally, we can change the duration of the animation to really appreciate its beauty:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation(.linear(duration: 2)) { self.zoomed.toggle() }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        
        if room.hasVideo &amp;amp;&amp;amp; !zoomed {
            Image(systemName: "video.fill")
                .font(.title)
                .padding(.all)
                .transition(.move(edge: .leading))
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Great job!&lt;/p&gt;

&lt;p&gt;Commit the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding room details
&lt;/h2&gt;

&lt;p&gt;Now it's time to make the detailed view really detailed by adding some more information about each room, specifically, where exactly it's located. &lt;/p&gt;

&lt;p&gt;You already know how to do this.&lt;/p&gt;

&lt;p&gt;First, let's add a Text view with the building number above the Image view:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        
        Text("Building: \(room.building)")
            .bold()
            .frame(maxWidth: .infinity, alignment: .center)
        
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation(.linear(duration: 2)) { self.zoomed.toggle() }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        
        if room.hasVideo &amp;amp;&amp;amp; !zoomed {
            Image(systemName: "video.fill")
                .font(.title)
                .padding(.all)
                .transition(.move(edge: .leading))
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Next, let's add another one with the floor number:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        
        Text("Building: \(room.building)")
            .bold()
            .frame(maxWidth: .infinity, alignment: .center)
        
        Text("Floor: \(room.floor)")
            .italic()
            .frame(maxWidth: .infinity, alignment: .center)
        
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation(.linear(duration: 2)) { self.zoomed.toggle() }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        
        if room.hasVideo &amp;amp;&amp;amp; !zoomed {
            Image(systemName: "video.fill")
                .font(.title)
                .padding(.all)
                .transition(.move(edge: .leading))
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Both of these values come from our testData array defined in the &lt;strong&gt;Room.swift&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frrc7ivwz8bvwqawd2si9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frrc7ivwz8bvwqawd2si9.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578454974/codewithjan/swiftui-by-examples/swiftui-by-examples-48.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But, as you can see, those two texts ended up on each other. Luckily, there's an easy fix. All we need to do is wrapping those Text views in the VStack:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    ZStack(alignment: .topLeading) {
        
        VStack {
            Text("Building: \(room.building)")
                .bold()
                .frame(maxWidth: .infinity, alignment: .center)
            
            Text("Floor: \(room.floor)")
                .italic()
                .frame(maxWidth: .infinity, alignment: .center)
        }
        
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fill : .fit)
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                withAnimation(.linear(duration: 2)) { self.zoomed.toggle() }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        
        if room.hasVideo &amp;amp;&amp;amp; !zoomed {
            Image(systemName: "video.fill")
                .font(.title)
                .padding(.all)
                .transition(.move(edge: .leading))
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Finally, let's add the VStack some padding so the text isn't so cramped up at the top edge:&lt;/p&gt;

&lt;pre&gt;
VStack {
    Text("Building: \(room.building)")
        .bold()
        .frame(maxWidth: .infinity, alignment: .center)
    
    Text("Floor: \(room.floor)")
        .italic()
        .frame(maxWidth: .infinity, alignment: .center)
}
.padding(.all)
&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffk2c6kykwloxn9omjpic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffk2c6kykwloxn9omjpic.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455032/codewithjan/swiftui-by-examples/swiftui-by-examples-49.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it's perfect! Notice how both previews use the appropriate room information.&lt;/p&gt;

&lt;p&gt;Commit the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing the data source
&lt;/h2&gt;

&lt;p&gt;Our list of rooms is loaded from the static array, but we want to be able to add new rooms, delete them and change their order. This means that we need to change the source of data from the static array to the dynamic object.&lt;/p&gt;

&lt;p&gt;We will create a new file called &lt;strong&gt;RoomStore.swift&lt;/strong&gt; where we will define an object for storing our data so we can change them over time.&lt;/p&gt;

&lt;p&gt;CMD+N to create a new Swift file and save it as RoomStore:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjok26r3u88ibcy8koiov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjok26r3u88ibcy8koiov.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455088/codewithjan/swiftui-by-examples/swiftui-by-examples-50.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8gbemxpks1qdtcxf5fz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8gbemxpks1qdtcxf5fz.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455142/codewithjan/swiftui-by-examples/swiftui-by-examples-51.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, place this code inside instead of the default code:&lt;/p&gt;

&lt;pre&gt;
import SwiftUI
import Combine

class RoomStore {
    var rooms: [Room]
    
    init(rooms: [Room] = []) {
        self.rooms = rooms
    }
    
}
&lt;/pre&gt;

&lt;p&gt;This is just a regular Swift object with the &lt;strong&gt;rooms&lt;/strong&gt; property and class initializer.  Now we need to tell SwiftUI when the object based on this class changes. To do that, we just conform the class to the ObservableObject protocol:&lt;/p&gt;

&lt;pre&gt;
class RoomStore: ObservableObject {
    var rooms: [Room]
    
    init(rooms: [Room] = []) {
        self.rooms = rooms
    }
    
}
&lt;/pre&gt;

&lt;p&gt;Next, we need to define the &lt;strong&gt;objectWillChangeProperty&lt;/strong&gt; which will store the PassthroughSubject that broadcasts elements to the subscriber. That's why we need to import the Combine model. You can think of it as some kind of notification center.&lt;/p&gt;

&lt;pre&gt;
class RoomStore: ObservableObject {
    var rooms: [Room]
    
    init(rooms: [Room] = []) {
        self.rooms = rooms
    }
    
    let objectWillChange = PassthroughSubject&amp;lt;Void, Never&amp;gt;()
    
}
&lt;/pre&gt;

&lt;p&gt;Finally, we will add the &lt;strong&gt;willSet&lt;/strong&gt; method to our rooms so we can notify our subject when our rooms changed:&lt;/p&gt;

&lt;pre&gt;
class RoomStore: ObservableObject {
    var rooms: [Room] {
        willSet { objectWillChange.send() }
    }
    
    init(rooms: [Room] = []) {
        self.rooms = rooms
    }
    
    let objectWillChange = PassthroughSubject()
    
}
&lt;/pre&gt;

&lt;p&gt;Now, go back to the &lt;strong&gt;ContentView.swift&lt;/strong&gt; file and update the code.&lt;/p&gt;

&lt;p&gt;Instead of rooms array, we will use our RoomStore object:&lt;/p&gt;

&lt;pre&gt;
struct ContentView: View {
    &lt;del&gt;var rooms: [Room] = []&lt;/del&gt;
    @ObservedObject var store = RoomStore()
    
    var body: some View {
        NavigationView {
            List(rooms) { room in
                RoomCell(room: room)
            }
        .navigationBarTitle("Rooms")
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Notice that we use the &lt;strong&gt;@ObservedObject&lt;/strong&gt; when we define the store property. This tells SwiftUI to listen for changes to this property.&lt;/p&gt;

&lt;p&gt;Let's also update our preview accordingly:&lt;/p&gt;

&lt;pre&gt;
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        &lt;del&gt;ContentView(rooms: testData)&lt;/del&gt;
        ContentView(store: RoomStore(rooms: testData))
    }
}
&lt;/pre&gt;

&lt;p&gt;And finally, we'll update our list to pull the rooms from the RoomStore:&lt;/p&gt;

&lt;pre&gt;
struct ContentView: View {
    @ObservedObject var store = RoomStore()
    
    var body: some View {
        NavigationView {
            List(store.rooms) { room in
                RoomCell(room: room)
            }
        .navigationBarTitle("Rooms")
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Great job!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmy8ci2tu84dra7u00jr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmy8ci2tu84dra7u00jr.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455200/codewithjan/swiftui-by-examples/swiftui-by-examples-52.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the preview, everything works just fine as before, only this time, we pull rooms from our object instead of the array. This means we are ready to add our editing support since we no longer use a static source of data. And we will start by adding a button to our list that will allow us to add new rooms.&lt;/p&gt;

&lt;p&gt;But before that, commit the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a new room
&lt;/h2&gt;

&lt;p&gt;First, let's add a ForEach that will create a view for each item in its collection:&lt;/p&gt;

&lt;pre&gt;
struct ContentView: View {
    @ObservedObject var store = RoomStore()
    
    var body: some View {
        NavigationView {
              &lt;del&gt;List(store.rooms) { room in&lt;/del&gt;
                &lt;del&gt;RoomCell(room: room)&lt;/del&gt;
              &lt;del&gt;}&lt;/del&gt;
              List {
                ForEach (store.rooms) { room in
                    RoomCell(room: room)
                }
              }
        .navigationBarTitle("Rooms")
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Now we can have a static element, that is our button alongside the list of rooms. So let's add the button. Drag it from the library right above the ForEach inside the List:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbpebw0eiyc7cjyo1aep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbpebw0eiyc7cjyo1aep.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455281/codewithjan/swiftui-by-examples/swiftui-by-examples-53.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to place it in the right spot. This is what it looks like in the code:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List {
            Button(action: {}) {
                Text("Button")
            }
            ForEach (store.rooms) { room in
                RoomCell(room: room)
            }
        }
    .navigationBarTitle("Rooms")
    }
}
&lt;/pre&gt;

&lt;p&gt;Now, let's update the Text so it shows "Add Room" instead of just "Button" and also, let's add a new method to specify the action for that button:&lt;/p&gt;

&lt;pre&gt;
struct ContentView: View {
    @ObservedObject var store = RoomStore()
    
    var body: some View {
        NavigationView {
            List {
                Button(action: {}) {
                    Text("&lt;del&gt;Button&lt;/del&gt;Add Room")
                }
                ForEach (store.rooms) { room in
                    RoomCell(room: room)
                }
            }
        .navigationBarTitle("Rooms")
        }
    }
    
    func addRoom() {
            store.rooms.append(Room(name: "Hall 2", building: "A", floor: "3", capacity: 2000, hasVideo: false, imageName: "room10"))
    }
}
&lt;/pre&gt;

&lt;p&gt;The &lt;strong&gt;addRoom()&lt;/strong&gt; method just appends a new instance of the Room struct to our object. We'll call it Hall 2 inside the building A on floor 3 with the capacity of 2000 seats and the image is room10.&lt;/p&gt;

&lt;p&gt;Finally, we'll update the action of our button so it uses this new addRoom() method:&lt;/p&gt;

&lt;pre&gt;    
struct ContentView: View {
    @ObservedObject var store = RoomStore()
    
    var body: some View {
        NavigationView {
            List {
                Button(action: &lt;del&gt;{}&lt;/del&gt;addRoom) {
                    Text("Add Room")
                }
                ForEach (store.rooms) { room in
                    RoomCell(room: room)
                }
            }
        .navigationBarTitle("Rooms")
        }
    }
    
    func addRoom() {
            store.rooms.append(Room(name: "Hall 2", building: "A", floor: "3", capacity: 2000, hasVideo: false, imageName: "room10"))
    }
}
&lt;/pre&gt;

&lt;p&gt;Great job! Now, go to live mode and tap the Add Room button. A new room will be added at the bottom of the list and you can even tap it to reveal its details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfv559mitykrd2rmrzwj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmfv559mitykrd2rmrzwj.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455340/codewithjan/swiftui-by-examples/swiftui-by-examples-54.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, let's style our list so it looks even better.&lt;/p&gt;

&lt;p&gt;To do that, we'll just put the &lt;strong&gt;Add Room&lt;/strong&gt; button and the list of rooms into separate sections and style our list and SwiftUI will do the rest for us.&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List {
            Section {
                Button(action: addRoom) {
                    Text("Add Room")
                }
            }
            Section {
                ForEach (store.rooms) { room in
                    RoomCell(room: room)
                }
            }
        }
        .navigationBarTitle("Rooms")
        .listStyle(GroupedListStyle())
        
    }
}
&lt;/pre&gt;

&lt;p&gt;Now it looks way better:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fry3gexxy60fazf3slrbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fry3gexxy60fazf3slrbo.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455408/codewithjan/swiftui-by-examples/swiftui-by-examples-55.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, commit the changes so we can move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deleting items
&lt;/h2&gt;

&lt;p&gt;Let's add the deletion feature to our list.&lt;/p&gt;

&lt;p&gt;First, we'll define a new method below the &lt;strong&gt;addRoom()&lt;/strong&gt; method and call it the &lt;strong&gt;deleteRoom().&lt;/strong&gt; We'll pass in some Offsets. Than we will tell the store to remove the room at those offsets:&lt;/p&gt;

&lt;pre&gt;
func addRoom() {
      store.rooms.append(Room(name: "Hall 2", building: "A", floor: "3", capacity: 2000, imageName: "room10"))
}
    
func deleteRoom(at offsets: IndexSet) {
      store.rooms.remove(atOffsets: offsets)
}
&lt;/pre&gt;

&lt;p&gt;Then, we'll add the &lt;strong&gt;.onDelete&lt;/strong&gt; modifier to the ForEach and we'll pass in as a parameter our deleteRoom() method:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List {
            Section {
                Button(action: addRoom) {
                    Text("Add Room")
                }
            }
            Section {
                ForEach (store.rooms) { room in
                    RoomCell(room: room)
                }
                .onDelete(perform: deleteRoom)
            }
        }
        .navigationBarTitle("Rooms")
        .listStyle(GroupedListStyle())
        
    }
}
&lt;/pre&gt;

&lt;p&gt;Now, go to the live mode, swipe on one of the rows and click to delete it! Or, you can even swipe all the way to the left as you might expect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqml1ut92xk5qncs7lcib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqml1ut92xk5qncs7lcib.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455536/codewithjan/swiftui-by-examples/swiftui-by-examples-56.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reordering items
&lt;/h2&gt;

&lt;p&gt;Let's take our list to the edit mode so we can not only delete items but reorder them as well.&lt;/p&gt;

&lt;p&gt;First, let's create the Edit button via navigationBarItems like this:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List {
            Section {
                Button(action: addRoom) {
                    Text("Add Room")
                }
            }
            Section {
                ForEach (store.rooms) { room in
                    RoomCell(room: room)
                }
                .onDelete(perform: deleteRoom)
            }
        }
        .navigationBarTitle("Rooms")
        .navigationBarItems(trailing: EditButton())
        .listStyle(GroupedListStyle())
        
    }
}
&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55gh6f2srbtgihw9nxhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F55gh6f2srbtgihw9nxhg.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455599/codewithjan/swiftui-by-examples/swiftui-by-examples-57.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let's add a new &lt;strong&gt;moveRoom()&lt;/strong&gt; method for moving the item and we'll move from the source which is IndexSet to a destination which is Integer. And inside, we'll call our store and tell it to move the item: &lt;/p&gt;

&lt;pre&gt;
func addRoom() {
    store.rooms.append(Room(name: "Hall 2", building: "A", floor: "3", capacity: 2000, imageName: "room10"))
}

func deleteRoom(at offsets: IndexSet) {
    store.rooms.remove(atOffsets: offsets)
}

func moveRoom(from source: IndexSet, to destination: Int) {
    store.rooms.move(fromOffsets: source, toOffset: destination)
}
&lt;/pre&gt;

&lt;p&gt;Finally, just like before, we'll add another modifier and tell it to call our newly created method:&lt;/p&gt;

&lt;pre&gt;
var body: some View {
    NavigationView {
        List {
            Section {
                Button(action: addRoom) {
                    Text("Add Room")
                }
            }
            Section {
                ForEach (store.rooms) { room in
                    RoomCell(room: room)
                }
                .onDelete(perform: deleteRoom)
                .onMove(perform: moveRoom)
            }
        }
        .navigationBarTitle("Rooms")
        .navigationBarItems(trailing: EditButton())
        .listStyle(GroupedListStyle())
        
    }
}
&lt;/pre&gt;

&lt;p&gt;Now, go back to the live mode, click the Edit button and try to move the items around. Also, you can tap the delete icon the reveal Delete button and remove the item:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fosralw5bv70owzmp7wiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fosralw5bv70owzmp7wiw.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455661/codewithjan/swiftui-by-examples/swiftui-by-examples-58.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnud9y3dobipgcdj0o9o8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnud9y3dobipgcdj0o9o8.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455730/codewithjan/swiftui-by-examples/swiftui-by-examples-59.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding more previews
&lt;/h2&gt;

&lt;p&gt;We'll add more previews the same way we already did in the &lt;strong&gt;RoomDetail.swift&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;So, let's wrap our ContentView inside Group and add another ContentView:&lt;/p&gt;

&lt;pre&gt;
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(store: RoomStore(rooms: testData))
            
            ContentView(store: RoomStore(rooms: testData))
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Now, with the second preview, we can change the environment to use much larger size:&lt;/p&gt;

&lt;pre&gt;
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(store: RoomStore(rooms: testData))
            
            ContentView(store: RoomStore(rooms: testData))
                .environment(\.sizeCategory, .extraExtraExtraLarge)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;That's pretty cool, huh?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgw1viigmorj2iv9e9lot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgw1viigmorj2iv9e9lot.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455799/codewithjan/swiftui-by-examples/swiftui-by-examples-60.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's test how our app would look like with the dark mode. We'll create yet another preview and use the dark color scheme:&lt;/p&gt;

&lt;pre&gt;
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView(store: RoomStore(rooms: testData))
            
            ContentView(store: RoomStore(rooms: testData))
                .environment(\.sizeCategory, .extraExtraExtraLarge)
            
            ContentView(store: RoomStore(rooms: testData))
                .environment(\.colorScheme, .dark)
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevjaqlrw27xgv02iykn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevjaqlrw27xgv02iykn5.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455881/codewithjan/swiftui-by-examples/swiftui-by-examples-61.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when you test the app in the live mode, everything will work as expected. So, let's add a new Room, check its details, move it to the top and finally delete it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzugtjgys28lsy42cifv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzugtjgys28lsy42cifv.png" alt="https://res.cloudinary.com/zavrelj/image/upload/v1578455941/codewithjan/swiftui-by-examples/swiftui-by-examples-62.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect! I hope you've learned a lot today. &lt;/p&gt;

&lt;p&gt;If you want to support me, please, check my courses on &lt;a href="https://www.udemy.com/user/jan-zavrel/" rel="noopener noreferrer"&gt;Udemy&lt;/a&gt; and &lt;a href="https://www.skillshare.com/r/user/zavreldotnet" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>swiftui</category>
    </item>
    <item>
      <title>Build a Simple Contact List with SwiftUI</title>
      <dc:creator>HZ</dc:creator>
      <pubDate>Fri, 06 Dec 2019 06:50:02 +0000</pubDate>
      <link>https://dev.to/zavrelj/build-a-simple-contact-list-with-swiftui-431d</link>
      <guid>https://dev.to/zavrelj/build-a-simple-contact-list-with-swiftui-431d</guid>
      <description>&lt;p&gt;In WWDC 2019 Apple introduced SwiftUI, which is an innovative and simple way to build user interfaces across all Apple platforms.&lt;/p&gt;

&lt;p&gt;SwiftUI is powered by Swift language and allows you to use the same tools and APIs for developing applications for macOS, iOS, iPadOS, tvOS and watchOS.&lt;/p&gt;

&lt;p&gt;SwiftUI uses a declarative syntax so you can simply state what your user interface should do. Before the introduction of SwiftUI, developers used imperative UI which was prone to errors because it required developers to track the state of the code. For example two states of Boolean, like on or off.&lt;/p&gt;

&lt;p&gt;In order to use SwiftUI with all the features it offers, you need to run macOS X 15 or newer and Xcode 11 or newer. Xcode 11 includes new design tools, drag &amp;amp; drop of controls and previews which allow you to instantly see the changes as you type your code which is a huge time saver.&lt;/p&gt;

&lt;p&gt;If you’ve developed for iOS platform before SwiftUI, you are probably familiar with Interface Builder and storyboards.&lt;/p&gt;

&lt;p&gt;Even though this helps to visualize the code a lot, they have one huge problem. Interface Builder doesn’t know about the Swift code and vice versa. That’s why it was pretty easy before SwiftUI to delete the action in Interface Builder, and since Interface Builder didn’t care about the code, the code would still compile.&lt;/p&gt;

&lt;p&gt;Simply put, Interface Builder and Swift were always totally separate tools, but SwiftUI changes this a big way. As a Swift-only framework, SwiftUI solves many old problems with Swift and Interface Builder.&lt;/p&gt;

&lt;p&gt;Here are two of the most important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  you no longer have to worry about calling non-existent functions, because the user interface is checked by the compiler&lt;/li&gt;
&lt;li&gt;  you no longer have to argue about programmatic or storyboard design, you get both at the same time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve created a rather short class to show you exactly how to use SwiftUI by creating a simple contact list. This app won’t earn you and design award, but it will demonstrate the power and ease of use SwiftUI offers over the old approach.&lt;/p&gt;

&lt;p&gt;Here's the link: &lt;a href="https://skl.sh/2XInPhG" rel="noopener noreferrer"&gt;https://skl.sh/2XInPhG&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With SwiftUI, Apple wrote a whole new and pretty exciting chapter to its iOS and macOS development book. I hope you’ll like using SwiftUI as much as I do! 🙂&lt;/p&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>swiftui</category>
      <category>xcode</category>
    </item>
    <item>
      <title>From WordPress to Ghost in the JAMstack</title>
      <dc:creator>HZ</dc:creator>
      <pubDate>Thu, 05 Dec 2019 11:22:11 +0000</pubDate>
      <link>https://dev.to/zavrelj/from-wordpress-to-ghost-in-the-jamstack-2gld</link>
      <guid>https://dev.to/zavrelj/from-wordpress-to-ghost-in-the-jamstack-2gld</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR version:&lt;/strong&gt;&lt;br&gt;
If you're tech-savvy, just grab &lt;a href="https://www.zavrel.net/headless-ghost-setup" rel="noopener noreferrer"&gt;this code&lt;/a&gt; and you'll figure it out immediately.&lt;/p&gt;

&lt;p&gt;The rest of you, just grab this free online tutorial where I explain the whole process step by step: &lt;a href="https://skl.sh/2YgFBub" rel="noopener noreferrer"&gt;https://skl.sh/2YgFBub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Longer version:&lt;/strong&gt;&lt;br&gt;
I know that you don't have time for meaningless small-talk so let's get to it. In this straight-to-the-point article, I will show you how to save hundreds of dollars per year by transferring your slow and expensive WordPress-based website to a fast, secure and free static Ghost-based blog.&lt;/p&gt;

&lt;p&gt;Instead of paying your hard-earned money for expensive web hosting and some more for premium plugins only to get mediocre performance at best, you'll pay nothing and get the best solution the current technology offers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdvrwtp6abh0jpvbjz2wa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdvrwtp6abh0jpvbjz2wa.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently switched one of my WordPress website to this modern solution and this is what happened. This is your SEO holy grail and the ticket to the top of Google results because speed is everything and you can't possibly get any better results.&lt;/p&gt;

&lt;p&gt;This is also something I was never able to achieve with WordPress, no matter how many hacks I tried, what SEO plugins I bought, how much I optimized images, Javascript and CSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvq4wo8ejttd3r46lneu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvq4wo8ejttd3r46lneu.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, there are some managed hosting services that will take your WordPress to a hundred percent in a speed test, but you will pay a small fortune for it while my solution is a hundred percent free of charge. You won't pay for web hosting, you won't pay for any special plugins, you'll get everything for free including the state of the art cloud-based image storage for your posts.&lt;/p&gt;

&lt;p&gt;With my solution, you'll get the best of both worlds money can buy and yet you won't spend a single penny:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A distraction-free state of the art CMS writers simply love for its ease of use&lt;/li&gt;
&lt;li&gt;Static, fast and secure blog hosted on Netlify's blazingly fast CDN network&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means that you can finally ditch your slow and expensive WordPress blog that drives you crazy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fak6hng0znzxiqzcvosy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fak6hng0znzxiqzcvosy9.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To achieve this future-proof professional publishing state, we will turn a regular Ghost CMS into a static blog and serve it from CDN.&lt;/p&gt;

&lt;p&gt;Basically, we'll take the best distraction-free editor out there, which is the Koenig editor from Ghost CMS, and teach it how to upload post images directly to Cloudinary.com, your free CDN image repository.&lt;/p&gt;

&lt;p&gt;You'll be able to create all your content locally on your machine and once it's ready, we'll transform it with Gatsby to a blazingly fast static website that we'll deploy to the state-of-the-art CDN network provided by Netlify. And yes, your content will be delivered via a secured HTTPS connection, because we want Google to love your site as much as your readers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjksvzufvvhj0lfdmavhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjksvzufvvhj0lfdmavhj.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the basic overview of your future blogging experience:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You will write your blog posts in your local Ghost CMS running in Docker container to keep your main operating system clean and fast.&lt;/li&gt;
&lt;li&gt;All the images will be automatically uploaded to Cloudinary CDN for the fast delivery and all sorts of other amazing features.&lt;/li&gt;
&lt;li&gt;Once you'll publish your new post, you're ready to take it from your local Ghost to the Internet.&lt;/li&gt;
&lt;li&gt;First, you will expose your localhost to the Internet.&lt;/li&gt;
&lt;li&gt;Next, you will deploy your blog to Netlify which means that Gatsby static generator sitting in your GitHub account will crunch the code and spit out the static version of your Ghost blog to Netlify's CDN.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I get it, at first, this might be quite intimidating, especially for total beginners who have no idea what's Gatsby or Docker.&lt;/p&gt;

&lt;p&gt;That's why I wanted to make the whole process extremely easy (even for total beginners), so I have decided to create a special docker-compose recipe and give you the whole setup package, so you'll be up and running in no time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnulbj0kymi5uhm0ul4p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnulbj0kymi5uhm0ul4p.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of explaining it all in a text form, I created a simple video tutorial which I published on Skillshare where you can get it for free via this link: &lt;a href="https://skl.sh/2YgFBub" rel="noopener noreferrer"&gt;https://skl.sh/2YgFBub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you'll enjoy this tutorial, ditch your expensive and slow WordPress and make your website a better experience not only for your readers but for Google as well, because speed is everything!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn72e1wefivc28x16x0qb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn72e1wefivc28x16x0qb.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I really want to help you, so if you get stuck, especially on Windows, let me know so I can guide you. I'm sure it's well worth the effort so you'll never ever have to decide which plan is just enough but not too bad...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fit4rpq4vhoy91jr5gtlj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fit4rpq4vhoy91jr5gtlj.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>wordpress</category>
      <category>gatsby</category>
      <category>ghost</category>
    </item>
    <item>
      <title>Docker and Docker Compose for PHP Development with GitHub and Digital Ocean Deployment</title>
      <dc:creator>HZ</dc:creator>
      <pubDate>Mon, 25 Nov 2019 17:43:59 +0000</pubDate>
      <link>https://dev.to/zavrelj/docker-and-docker-compose-for-php-development-with-github-and-digital-ocean-deployment-52k9</link>
      <guid>https://dev.to/zavrelj/docker-and-docker-compose-for-php-development-with-github-and-digital-ocean-deployment-52k9</guid>
      <description>&lt;p&gt;I have always missed some easy to follow tutorials on Docker, so I have decided to create it myself. I hope it will help you understand why Docker is such a popular tool and why more and more developers are choosing Docker over Vagrant and other solutions.&lt;/p&gt;

&lt;p&gt;When it comes to PHP development, you have basically three options on how to approach the problem of preparing a development environment for your project. The oldest way is to install individual services on your development machine manually. With different versions of these services on staging and production environments, you can get into dependencies problems rather quickly. Also, it’s quite a challenge to manage different projects with different requirements on a single computer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ARq0p1Drq3VCDT1f0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ARq0p1Drq3VCDT1f0.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can solve this problem with virtual machines. Download VirtualBox and set up your environment individually for every project so they won’t interfere because they will be totally separated. When you need to deploy your work along with your environment to the remote server, you will just provision the whole virtual machine. Vagrant can help you with this process because it allows you to deploy your project directly. For example to a DigitalOcean Droplet. But the problem is that you are working with the full-blown operating systems even though they are virtualized.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AgQ-FncmmdWdrAYeo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AgQ-FncmmdWdrAYeo.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What if there is another way? What if you don’t need full operating system encapsulated in virtual machines to keep your projects separated and yet you would be able to have the same development environment everywhere, on your local machine, on the testing server and even on the production server. Welcome to the world of Docker!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AjfIk9mrphZ7HYgfx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AjfIk9mrphZ7HYgfx.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To better understand the difference between Docker and VM-based solution, take a look at the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Aip5rAUjldxn_RF_9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Aip5rAUjldxn_RF_9.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker can help you as a developer in three areas by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; eliminating the “it works on my machine” problem once and for all because it will package dependencies with your apps in the container for portability and predictability during development, testing, and deployment,&lt;/li&gt;
&lt;li&gt; allowing you to deploy both microservices and traditional apps anywhere without costly rewrites by isolating apps in containers; this will eliminate conflicts and enhance security,&lt;/li&gt;
&lt;li&gt; streamlining collaboration with system operators and getting updates into production faster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Interested? Great! Let’s give it a try.&lt;/p&gt;

&lt;h3&gt;
  
  
  I will make you a full-stack web developer in just 12 hours!
&lt;/h3&gt;

&lt;p&gt;This tutorial is a part of a much larger and far more detailed online course available for free on &lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt;. If you're new to Skillshare, you'll get premium access not only to this course for free but to &lt;strong&gt;all of my other courses as well and for a good measure, to over 22.000 courses&lt;/strong&gt; currently hosted on Skillshare. &lt;/p&gt;

&lt;p&gt;You won't be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Consider it a 2 months worth of premium education for free. It's like Netflix, only for life-long learners :-)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodewithjan.com%2Fimages%2Fposts%2Ftwdc-skillshare-cover-play.png" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Installation
&lt;/h3&gt;

&lt;p&gt;This one is easy, all you need is download a package for your operating system. In my case, I downloaded Docker for Mac. You will install Docker as any other application. Once you see the Docker happily humming in your top bar, you can start building awesomeness with me!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A4_7uhPGJcPDfc8SI.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A4_7uhPGJcPDfc8SI.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I will use Atom (&lt;a href="http://www.atom.io" rel="noopener noreferrer"&gt;www.atom.io&lt;/a&gt;) editor along with the terminal-plus package. If you want to follow my setup, be aware that you need to tweak the terminal-plus a bit to work. There is some minor issue, but it can be easily fixed like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go to &lt;strong&gt;~/.atom/packages/terminal-plus/package.json&lt;/strong&gt; and locate the dependencies section.&lt;/li&gt;
&lt;li&gt; Remove the commit id (#……) at the end of the pty.js entry: 
“pty.js”: “git+&lt;a href="https://github.com/jeremyramin/pty.js.git**#28f2667**%E2%80%9D%C2%A0" rel="noopener noreferrer"&gt;https://github.com/jeremyramin/pty.js.git**#28f2667**” &lt;/a&gt;
becomes 
“pty.js”: “git+&lt;a href="https://github.com/jeremyramin/pty.js.git%E2%80%9D" rel="noopener noreferrer"&gt;https://github.com/jeremyramin/pty.js.git”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to terminal and run these commands:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/.atom/packages/terminal-plus/
npm install
apm rebuild
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Restart the Atom editor and terminal-plus should work now! Of course, you can choose your own text editor and terminal client. Everything will work the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create your first Docker image
&lt;/h3&gt;

&lt;p&gt;The easiest way to create a Docker image is with the &lt;strong&gt;Dockerfile&lt;/strong&gt; which is something like a recipe for building an &lt;strong&gt;image&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: Image is something like a blueprint. You can use one blueprint to create many objects like cars or houses. Similarly, you can use one image to create many containers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a look at how easy it is to create a development environment based on PHP 5 and Apache webserver.&lt;/p&gt;

&lt;p&gt;Create a new folder on your Desktop and name it &lt;strong&gt;docker-apache-php5&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AVfCy8EZtpEHMWBe9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AVfCy8EZtpEHMWBe9.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside &lt;strong&gt;docker-apache-php5&lt;/strong&gt; folder, create new folder called &lt;strong&gt;src&lt;/strong&gt; where we will add a new file called &lt;em&gt;phpinfo.php&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Create this file and put this code inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  &lt;span class="nb"&gt;phpinfo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very simple php code that will call just one simple function, but this function will output a nice table with a detailed configuration of our development environment we are just creating.&lt;/p&gt;

&lt;p&gt;Create another file, this time directly inside &lt;strong&gt;docker-apache-php5&lt;/strong&gt; folder and call it &lt;strong&gt;Dockerfile&lt;/strong&gt; (just like this, no extension). Inside this file, we will write some directives for Docker.&lt;/p&gt;

&lt;p&gt;Your directory structure should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AInSB7a2grGSsS3be.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AInSB7a2grGSsS3be.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We want to build our development environment on PHP 5 and Apache webserver. The best way is to start with the image already available. In the case of PHP, there is probably no better source than the official image.&lt;/p&gt;

&lt;p&gt;Docker images are available on Docker Hub. Sign up for a free account and once you are in, you can search for images.&lt;/p&gt;

&lt;p&gt;Go ahead and type &lt;strong&gt;php&lt;/strong&gt; in the search box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Ak0ZL7qfnq7aKBbjT.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Ak0ZL7qfnq7aKBbjT.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;php official&lt;/strong&gt; image and let’s take a look at the details:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AG93xxjA2iINswIq7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AG93xxjA2iINswIq7.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We want to use &lt;strong&gt;5.6-apache&lt;/strong&gt;, version which is PHP 5.6 including the Apache webserver. This is convenient because we don’t need to install Apache separately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AdXDNGBU32i6Vk-7I.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AdXDNGBU32i6Vk-7I.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and click on the link &lt;a href="https://github.com/docker-library/php/blob/e573f8f7fda5d7378bae9c6a936a298b850c4076/5.6/apache/Dockerfile" rel="noopener noreferrer"&gt;(&lt;em&gt;5.6/apache/Dockerfile&lt;/em&gt;)&lt;/a&gt; which will get you to GitHub Repository of this image. Just take a look at the Dockerfile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AaGo138OGoAGAhEec.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AaGo138OGoAGAhEec.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don’t panic, our Dockerfile will have just a couple of lines because we will take advantage of the hard work of the PHP team and use their image. This doesn’t mean that you can’t just sit down and write your own image based on Debian Linux, but why would you want to waste your time, when you can just use what’s already done?&lt;/p&gt;

&lt;p&gt;In order to use this image, go to your Dockerfile and write this line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    FROM php:5.6-apache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says that our own image will be based on the &lt;strong&gt;5.6-apache&lt;/strong&gt; image created and maintained by the PHP team. Sweet.&lt;/p&gt;

&lt;p&gt;Type this line below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    COPY src/ /var/www/html/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure there is a space between &lt;strong&gt;src/&lt;/strong&gt; and &lt;strong&gt;/var&lt;/strong&gt;. It’s very important! Now, this line says that we want the content of the &lt;strong&gt;src&lt;/strong&gt; folder we have created a few minutes ago, to be copied to &lt;strong&gt;/var/www/html/&lt;/strong&gt; but you might wonder why and where it is located.&lt;/p&gt;

&lt;p&gt;This is the folder structure that will be created while our image will be built or more specifically while we will create a container from that image. I showed you the Dockerfile for &lt;strong&gt;5.6-apache&lt;/strong&gt; image on purpose. Remember the line &lt;strong&gt;FROM debian:jessie&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A_7UCBHzKxssWwSrq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A_7UCBHzKxssWwSrq.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our image will be based on a &lt;strong&gt;5.6-apache&lt;/strong&gt; image, but even this image is based on another image. It this case, it is &lt;strong&gt;debian:jessie&lt;/strong&gt; image. So basically, the PHP team grabbed &lt;strong&gt;debian:jessie&lt;/strong&gt; image and added their own modifications with their Dockerfile, like we are adding our own modifications to &lt;strong&gt;5.6-apache&lt;/strong&gt; image with our Dockerfile.&lt;/p&gt;

&lt;p&gt;The point is that we are all adding layers to the basic &lt;strong&gt;debian:jessie&lt;/strong&gt; image which is a Linux distribution and as you probably know, the Linux file system starts with root (&lt;strong&gt;/&lt;/strong&gt;) followed by specific subfolders. Mac OS is based on UNIX and it works similarly. Your Desktop is actually located in &lt;strong&gt;/Users/your-name/Desktop&lt;/strong&gt;. In my case, it is &lt;strong&gt;/Users/zavrelj/Desktop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, for web content, Apache web server uses a directory called &lt;strong&gt;html&lt;/strong&gt; which is stored inside &lt;strong&gt;www&lt;/strong&gt; directory inside &lt;strong&gt;var&lt;/strong&gt; directory. That’s how it is and because we know it, we can say that once our image with Apache web server is initialized or spun up to create a container, we can safely copy the content of our &lt;strong&gt;src&lt;/strong&gt; folder to &lt;strong&gt;/var/www/html&lt;/strong&gt; folder on Debian because it will be there since the Apache web server will create it during its own installation.&lt;/p&gt;

&lt;p&gt;Give yourself a pause and let this all sink. It’s a really important concept.&lt;/p&gt;

&lt;p&gt;Ok, the last line we will add to our Dockerfile looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    EXPOSE 80 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It says that we want the port 80 to be available for incoming requests.&lt;/p&gt;

&lt;p&gt;Your Dockerfile should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8o82f9L3qwGBQwHW.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8o82f9L3qwGBQwHW.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have this, save the changes, open your favorite terminal app. In the terminal, set your &lt;strong&gt;docker-apache-php5&lt;/strong&gt; folder as a working directory. I expect you to know how to work with the command line.&lt;/p&gt;

&lt;p&gt;If you don’t just open terminal and type&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    pwd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will show you your current working directory. If have you followed me step by step so far and you are on Mac computer, this command should get you to the right directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     cd ~/Desktop/docker-apache-php5 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy this code and paste it into the terminal, then hit Enter. To make sure, you are in the right directory, type &lt;strong&gt;ls&lt;/strong&gt; in the terminal and hit Enter. If you see something like this, you’re good to go:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Apt19bUum6A8AC-TJ.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Apt19bUum6A8AC-TJ.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s important to be in the right directory, where the Dockerfile is saved. We will now build our image from the Dockerfile.&lt;/p&gt;

&lt;p&gt;Type this line in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker build -t php-image . 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will build our image, &lt;strong&gt;-t&lt;/strong&gt; option lets you give the image a custom name, in our case, it will be &lt;strong&gt;php-image&lt;/strong&gt; because I want you from the very beginning to be able to make a distinction between images and containers. Finally, the dot at the end of this command means that the Dockerfile is located in the current directory. That’s why we wanted to get there!&lt;/p&gt;

&lt;p&gt;If everything went right, you should see something similar in your terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Akj6LNWPMjW6O2YgV.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Akj6LNWPMjW6O2YgV.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker has just created an image and assigned it an ID, in my case &lt;strong&gt;576a14c36bc9&lt;/strong&gt;.  To see the list of all your images, just type this command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker image ls 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, there are two images, and they are sorted by the date of creation. The first one is our image we have just built, the second one is the image Docker pulled from Docker Hub. As you can see, its name is &lt;strong&gt;php&lt;/strong&gt; and the tag is &lt;strong&gt;5.6-apache&lt;/strong&gt;, together it makes &lt;strong&gt;php:5.6-apache&lt;/strong&gt; which is exactly what we wrote in the first line of our Dockerfile! Docker needed to pull this image first in order to create our own image. That’s why we have two images even though we have created only one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8GzIRMiioETQzPNO.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8GzIRMiioETQzPNO.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we need to create a container from our &lt;strong&gt;php-image&lt;/strong&gt;. Our image is just like a snapshot. To be able to actually work with your services like PHP, you need to spin up the container from that image.&lt;/p&gt;

&lt;p&gt;Type this in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker run -p 80:80 -d --name php-container php-image 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a container from our &lt;strong&gt;php-image&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-p 80:80&lt;/strong&gt; is port mapping, remember how we exposed 80 in the Dockerfile? Well, now we need to tell the container to use the exposed port 80 and deliver its content to the port 80 of our localhost,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-d&lt;/strong&gt; stands for a detached mode which will bring the process to the background so you can still use the same terminal window,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;--name&lt;/strong&gt; allows us to give our container name of choice, otherwise, Docker would pick one for us randomly.&lt;/p&gt;

&lt;p&gt;And finally, at the end of this command is the name of the image from which we want to create our container.&lt;/p&gt;

&lt;p&gt;Remember that the name must be put only after all options! To make sure that your new container is up and running, type this command in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AK7Vz9_EHLisf96dV.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AK7Vz9_EHLisf96dV.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the container’s name, the image it was created from, ports, ID and status.&lt;/p&gt;

&lt;p&gt;Since our container is waiting for some work, let’s make it do its job! Open your web browser and type &lt;strong&gt;localhost/phpinfo.php&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You should get this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A1Q0J9DJdWgFEDoF8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A1Q0J9DJdWgFEDoF8.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It means that everything works and we are running PHP 5.6.30 on our local webserver! Great work!&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding database
&lt;/h3&gt;

&lt;p&gt;Unfortunately, this container won’t work with a database because all we have is PHP and Apache webserver. To add a database server to our development environment, like MySQL for example, we would have to create a container for a database and connect it to our &lt;strong&gt;php-container&lt;/strong&gt;, thus those two containers or rather services inside those containers could talk to each other.&lt;/p&gt;

&lt;p&gt;Let’s take a look at how this can be done. We will start again in Docker Hub and search for mysql:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AEOwc7q7ifguhpNov.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AEOwc7q7ifguhpNov.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And sure enough, there is an official repository maintained by MySQL team. Let’s create our own &lt;strong&gt;mysql-container&lt;/strong&gt; by running this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=secret -d mysql:latest 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run this code, Docker will first look for &lt;strong&gt;mysql:latest&lt;/strong&gt; image on your computer. If it’s not available, it will pull it from Docker Hub first and then spin up the container from it. This is very important because Docker is trying to save your disk space. If &lt;strong&gt;mysql:latest&lt;/strong&gt; image is already on your computer, Docker will use it instead of downloading yet another copy.&lt;/p&gt;

&lt;p&gt;Remember this, we will come back to this concept later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Ay2zsdQSjE33rMs7H.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Ay2zsdQSjE33rMs7H.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type &lt;strong&gt;docker ps&lt;/strong&gt; again. You should now see two containers and both are running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AYV7sxjXIvq0rxoXn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AYV7sxjXIvq0rxoXn.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This demonstrates, that you can immediately spin up a container from an already existing image. Only if you want to create your own image, you need to actually build it first and run it later to spin up a container from it.&lt;/p&gt;

&lt;p&gt;Let’s create some mysql code to see if mysql is working. Since we run PHP 5.6, we can use &lt;strong&gt;mysql_connect&lt;/strong&gt; function. Even though it’s deprecated in PHP 5 and completely removed from PHP 7, for our testing purposes, it will be just fine.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;src&lt;/strong&gt; directory, create a new file named &lt;strong&gt;mysql.php&lt;/strong&gt; and place this content in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;     &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

     &lt;span class="nv"&gt;$database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
     &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
     &lt;span class="nv"&gt;$password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
     &lt;span class="nv"&gt;$host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mysql"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

     &lt;span class="nv"&gt;$db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;mysql_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Cannot connect to the database server"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
     &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$db&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mysql_select_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$SQL_DBASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Sucessfully connected to the database server! Database Users selected!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
     &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very simple php code. It tries to connect to the database server with the credentials we provided. If the connection cannot be established, it will display an error. If the connection is successful and the database &lt;strong&gt;users&lt;/strong&gt; exists, it will display a success message.&lt;/p&gt;

&lt;p&gt;Now, try to go to &lt;strong&gt;localhost/mysql.php&lt;/strong&gt; in your web browser. You should get this message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AuGXi8m8Jr3CtKZa-.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AuGXi8m8Jr3CtKZa-.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our &lt;strong&gt;mysql.php&lt;/strong&gt; file can not be found, even though it is in the same directory as &lt;strong&gt;phpinfo.php&lt;/strong&gt; file and this one can be found just fine.&lt;/p&gt;

&lt;p&gt;The problem is that even though we added a new file and thus changed the content of our project, we are still running the old &lt;strong&gt;php-container&lt;/strong&gt; based on the original &lt;strong&gt;php-image&lt;/strong&gt; which has no clue about the changes we have just made.&lt;/p&gt;

&lt;p&gt;To fix this, we need to rebuild our &lt;strong&gt;php-image&lt;/strong&gt; and spin up a new container from this updated image. If you think now that this is a lot of hassle, it is, but just for now. You will truly appreciate a feature called &lt;strong&gt;volumes&lt;/strong&gt; I will introduce later, once you go with me through this hell.&lt;/p&gt;

&lt;p&gt;Stop the &lt;strong&gt;php-container&lt;/strong&gt; we have created from &lt;strong&gt;php-image&lt;/strong&gt; by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker stop php-container 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To list all containers, even those that are not running, use &lt;strong&gt;docker ps -a&lt;/strong&gt; command. You can see that &lt;strong&gt;php-container&lt;/strong&gt; has exited:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Abv4Ee3zcixpimT15.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Abv4Ee3zcixpimT15.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the container is stopped, we can remove it with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker rm php-container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can remove the container even while it is running, in that case, you need to add &lt;strong&gt;-f&lt;/strong&gt; option to the end of the command above. Once the &lt;strong&gt;php-container&lt;/strong&gt; is removed, you can remove the &lt;strong&gt;php-image&lt;/strong&gt; as well. If you tried to remove &lt;strong&gt;php-image&lt;/strong&gt; while the &lt;strong&gt;php-container&lt;/strong&gt; still existed, Docker would protest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker rmi php-image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, now we can rebuild our &lt;strong&gt;php-image&lt;/strong&gt; again and the only reason for that is to copy our new &lt;strong&gt;mysql.php&lt;/strong&gt; file into &lt;strong&gt;/var/www/html&lt;/strong&gt; folder. Remember the instruction from Dockerfile? Here it is again: &lt;strong&gt;COPY src/ /var/www/html/&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why we did all of this, to get our new &lt;strong&gt;mysql.php&lt;/strong&gt; copied from &lt;strong&gt;src&lt;/strong&gt; folder to &lt;strong&gt;/var/www/html&lt;/strong&gt; folder. There is another, way better solution, though, and we will get to it soon. Let’s build our image again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker build -t php-image .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and spin up the updated container from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker run -p 80:80 -d --name php-container php-image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, navigate to &lt;strong&gt;localhost/mysql.php&lt;/strong&gt; from your web browser. The file apparently exists, but we have another problem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AJfCpCV3qxrNnPPs0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AJfCpCV3qxrNnPPs0.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PHP image is very lightweight, it doesn’t usually include PHP extensions and mysql is one of those missing. That means that PHP doesn’t know about any function called &lt;strong&gt;mysql_connect()&lt;/strong&gt;. To fix this, we need to add &lt;strong&gt;mysql&lt;/strong&gt; extension to our &lt;strong&gt;php-image&lt;/strong&gt; first. But don’t be scared, we won’t undergo the same painful process again.&lt;/p&gt;

&lt;p&gt;You can directly rebuild the image and then run the container without the painful process of stopping the container, removing the container and rebuilding the image. But I didn’t tell you sooner because I wanted you to try all these commands so you know how to manage containers and images. I hope you will forgive me this pesky move 🙂&lt;/p&gt;

&lt;p&gt;Go to your Dockerfile and add this line at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     RUN docker-php-ext-install mysql 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add mysql extension to our PHP image. Now just run this command in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker build -t php-image . 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that in Step 4/4, a mysql extension has been added to our image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ApfNEkIE-wzJ5307E.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ApfNEkIE-wzJ5307E.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you list all images with &lt;strong&gt;docker image ls&lt;/strong&gt;, you can see that &lt;strong&gt;php-image&lt;/strong&gt; has been created only a few seconds ago. This means that if you build the image with the same name, the original image is overwritten with the new one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A05g0naxZp5Fe8vEw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A05g0naxZp5Fe8vEw.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same can’t be done with the container, though. If you try and run this command now while the original container is still running…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker run -p 80:80 -d --name php-container php-image 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…you will get this error message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AWGD2nnHSo7UXTF0V.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AWGD2nnHSo7UXTF0V.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need to stop and remove the currently running &lt;strong&gt;php-container&lt;/strong&gt; first. As I mentioned already, you can do this at the same time by using -f option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker rm php-container -f 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can spin up the &lt;strong&gt;php-container&lt;/strong&gt; again, but this time the updated &lt;strong&gt;php-image&lt;/strong&gt; will be used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker run -p 80:80 -d --name php-container php-image 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;NOTE: You might wonder if you could just spin up a container with a different name and kept the original one running.&lt;br&gt;&lt;br&gt;
You could do that! The problem is that you would have to map a different port as well because&lt;/em&gt; &lt;strong&gt;&lt;em&gt;port 80&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;would be still taken by the original&lt;/em&gt; &lt;strong&gt;&lt;em&gt;php-container&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;, thus unavailable for new mapping.&lt;br&gt;&lt;br&gt;
This could be of course solved by using for example&lt;/em&gt; &lt;strong&gt;&lt;em&gt;-p 81:80&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;instead of&lt;/em&gt; &lt;strong&gt;&lt;em&gt;-p 80:80&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;. Finally, you would have to explicitly type the port to the web browser like this:&lt;/em&gt; &lt;strong&gt;&lt;em&gt;localhost:81/mysql.php&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Port 80&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;is the default one, that’s why it doesn’t have to be written explicitly, unlike other ports.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;OK, navigate to &lt;strong&gt;localhost/mysql.php&lt;/strong&gt; from your web browser again. Even though we get another warning now, we are getting closer because the new error message comes directly from &lt;strong&gt;mysql_connect()&lt;/strong&gt; function. That means that it exists and PHP knows about it. But it seems like there is a problem with a network address:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AI4gnxiuZveXq4puv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AI4gnxiuZveXq4puv.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason for this error is the fact that we have two separate containers. One for PHP (&lt;strong&gt;php-container&lt;/strong&gt;) and one for MySQL (&lt;strong&gt;mysql-container&lt;/strong&gt;). And they don’t know about each other, they don’t talk to each other. Let’s fix this. Stop &lt;strong&gt;php-container&lt;/strong&gt; once again and remove it at the same time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker rm php-container -f 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker run -p 80:80 -d --name php-container --link mysql-container:mysql php-image 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are already familiar with this code except for the link part. This says that we want to link our &lt;strong&gt;php-container&lt;/strong&gt; with &lt;strong&gt;mysql-container&lt;/strong&gt;. Now, navigate to &lt;strong&gt;localhost/mysql.php&lt;/strong&gt; from your web browser, this time you should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A7dTBPS6FgX8p10b1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A7dTBPS6FgX8p10b1.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect! Now, in order to be able to modify the content of our &lt;strong&gt;src&lt;/strong&gt; folder without the need to rebuild images all the time, we will add &lt;strong&gt;-v&lt;/strong&gt; option to our docker run command. So for the last time, stop and remove php-container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker rm php-container -f 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it again with this new option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker run -p 80:80 -d -v ~/Desktop/docker-apache-php5/src/:/var/www/html/ --name php-container --link mysql-container:mysql php-image 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This option should be quite familiar. We used something similar in our Dockerfile to tell our image to copy the content of our &lt;strong&gt;src&lt;/strong&gt; folder to the Apache webserver default directory inside the container.&lt;/p&gt;

&lt;p&gt;Well, this time, we will create the volume, which means that those two locations will be in sync. Actually, we will mount our folder saved in Desktop to the location inside the container. Once you make any kind of change in &lt;strong&gt;src&lt;/strong&gt; folder, it will be automatically available in &lt;strong&gt;/var/www/html&lt;/strong&gt; folder in Apache webserver.&lt;/p&gt;

&lt;p&gt;Let’s test this! Go to your &lt;strong&gt;mysql.php&lt;/strong&gt; file and add “AMAZING!” at the end of the echo like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nc"&gt;Successfully&lt;/span&gt; &lt;span class="n"&gt;connected&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;Database&lt;/span&gt; &lt;span class="nc"&gt;Users&lt;/span&gt; &lt;span class="n"&gt;selected&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="no"&gt;AMAZING&lt;/span&gt;&lt;span class="o"&gt;!**&lt;/span&gt;&lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file and refresh the browser! Isn’t that amazing? 🙂&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Compose
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AVNJIHdALyPrMlhkV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AVNJIHdALyPrMlhkV.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far, we did it all manually. We configured and created images, we created containers and link them together. If you work with two or three containers, it is doable, even though we have spent quite some time with this. However, if you need to set up the environment with many more containers, it will become very tedious to go through all those steps manually every time.&lt;/p&gt;

&lt;p&gt;Luckily, there is a better way. Docker Compose is a tool for defining and running multi-container Docker applications. It allows you to create a YAML configuration file where you will configure your application’s services, and define all the steps necessary to build images, spin up containers and link them together. Finally, once all this is done, you will just set it all in motion with a single command.&lt;/p&gt;

&lt;p&gt;Let’s take a look at how this works. This time, we will create the LEMP stack which will consist of Linux, PHP 7, Ngnix, and MySQL. It is generally recommended to have one process, or microservice per container, so we will separate things here. We will create six containers and orchestrate them with Docker Compose.&lt;/p&gt;

&lt;p&gt;As we already did in the previous section, we will again use official images and extend them with our Dockerfiles. First, let’s delete all containers and images, so we can start with a clean slate.&lt;/p&gt;

&lt;p&gt;To list all containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker ps -a 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To delete containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker rm php-container -f  
     docker rm mysql-container -f 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To list all images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker image ls 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To delete images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker rmi php-image:latest -f  
     docker rmi php:5.6-apache -f  
     docker rmi mysql:latest -f 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;-f&lt;/strong&gt; option will force deletion even if the container is running or the image is in use.&lt;/p&gt;

&lt;p&gt;If by any chance you won’t be able to delete container or image by its name, use its ID instead. This is actually the only viable alternative if you happen to have an image with no name:   &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AA-NhzY64B4wxW1XY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AA-NhzY64B4wxW1XY.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;List all containers with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker ps -a 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and all images with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker image ls 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All clear? Great! Let’s begin! Go to your Desktop and create a new folder called &lt;strong&gt;docker-ngnix-php7&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nginx
&lt;/h3&gt;

&lt;p&gt;Let’s start with a web server. Instead of Apache, we will use Nginx this time. First, we will check if there is any official image on Docker Hub. And sure enough, here it is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AWYm6VTjMvJmbHiG5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AWYm6VTjMvJmbHiG5.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will choose the tag &lt;strong&gt;latest&lt;/strong&gt;, so I hope you remember, that the name of the image and the tag go together like this: &lt;strong&gt;nginx:latest&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, create a new file in your &lt;strong&gt;docker-nginx-php7&lt;/strong&gt; directory and save it as &lt;strong&gt;docker-compose.yml&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Inside, write this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     nginx:   
      image: nginx:latest  
      container_name: nginx-container  
      ports:   
       - 80:80 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be somewhat familiar. Remember when we ran mysql image? We used this command in the terminal: &lt;strong&gt;docker run -p 80:80 -d --name php-container php-image&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, instead of running this command, we will take the options and save them in a configuration file. Then, we will let Docker Compose run commands for us by following the instructions in this file. Save the file. This is what it should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8jG6vnXpKHpd5Z49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8jG6vnXpKHpd5Z49.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to the &lt;strong&gt;docker-nginx-php7&lt;/strong&gt; directory in your terminal and run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker-compose up -d 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;-d&lt;/strong&gt; option still means detached, nothing new here.&lt;/p&gt;

&lt;p&gt;Docker Compose will pull Nginx image from Docker Hub, create a container and give it a name we specified. Then, it will start the container for us. Docker Compose will do all of these steps automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ACdicIv75BST4DK7o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ACdicIv75BST4DK7o.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I gave the container a specific name just for educational purposes here, so we can easily identify it. But it’s not a good practice in general because container names must be unique. If you specify a custom name, you won’t be able to scale that service beyond one container, so it’s probably better to let Docker assign automatically generated names instead. But, in our case, I want you to understand how things are working.&lt;/p&gt;

&lt;p&gt;Use the familiar &lt;strong&gt;docker ps&lt;/strong&gt; command to see the list of running containers. Write down the IP address assigned to &lt;strong&gt;ngnix-container&lt;/strong&gt; and navigate to this address with your web browser. You don’t have to write the port number since 80 is a default value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A1o-WAVxqTSFlqnJZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A1o-WAVxqTSFlqnJZ.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see your Nginx web server running:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8fUvcF0TCRmhTzt3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8fUvcF0TCRmhTzt3.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was easy, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP
&lt;/h3&gt;

&lt;p&gt;Let’s say that we want to add the PHP to the mix and we want it to be automatically downloaded, configured and started. We also want to modify our Nginx web server a bit. You know the drill. If you want to modify the official image and add your own changes, you need to use Dockerfile as we already did in the previous section.&lt;/p&gt;

&lt;p&gt;Let’s do this again. First, we will create a new directory inside our &lt;strong&gt;docker-ngnix-php7&lt;/strong&gt; folder and name it &lt;strong&gt;nginx&lt;/strong&gt;. In this directory, we will save a new &lt;strong&gt;Dockerfile&lt;/strong&gt;. Next, we will create a new &lt;strong&gt;index.php&lt;/strong&gt; file which will be saved in &lt;strong&gt;www/html&lt;/strong&gt; directory inside &lt;strong&gt;docker-ngnix-php7&lt;/strong&gt; folder with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;     &lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;  
     &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;  
      &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Hello World!&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;  
     &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;  

     &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;  
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello World!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;  
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php echo 'We are running PHP, version: ' . phpversion(); ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;  
     &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple page will help us test if PHP is running.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: If you use Atom editor, you can create&lt;/em&gt; &lt;strong&gt;&lt;em&gt;new&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;file and the whole new directory structure at the same time! Just click with the right mouse button on the name of&lt;/em&gt; &lt;strong&gt;&lt;em&gt;docker-nginx-php7&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;folder in left pane in Atom, choose&lt;/em&gt; &lt;strong&gt;&lt;em&gt;New File&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;and instead of typing just the name of the file, type the whole path&lt;/em&gt; &lt;strong&gt;&lt;em&gt;www/html/index.php&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;. Atom will create the file for you and both directories as well!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A7HgWp2itNFxa1hoT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A7HgWp2itNFxa1hoT.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your folder structure should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AzqUL8p1LzAjix6qx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AzqUL8p1LzAjix6qx.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To configure our Nginx web server, we will use &lt;strong&gt;default.conf&lt;/strong&gt;, so create this file and save it in &lt;strong&gt;nginx&lt;/strong&gt; folder. Now add this content inside &lt;strong&gt;default.conf&lt;/strong&gt; and save it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     server {  

     listen 80 default_server;  
     root /var/www/html;  
     index index.html index.php;  

     charset utf-8;  

     location / {  
      try_files $uri $uri/ /index.php?$query_string;  
     }  

     location = /favicon.ico { access_log off; log_not_found off; }  
     location = /robots.txt { access_log off; log_not_found off; }  

     access_log off;  
     error_log /var/log/nginx/error.log error;  

     sendfile off;  

     client_max_body_size 100m;  

     location ~ .php$ {  
      fastcgi_split_path_info ^(.+.php)(/.+)$;  
      fastcgi_pass php:9000;  
      fastcgi_index index.php;  
      include fastcgi_params;  
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;  
      fastcgi_intercept_errors off;  
      fastcgi_buffer_size 16k;  
      fastcgi_buffers 4 16k;  
    }  

     location ~ /.ht {  
      deny all;  
     }  
    } 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now back to Dockerfile for Nginx. Write these two lines in it and save the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    FROM nginx:latest   
    COPY ./default.conf /etc/nginx/conf.d/default.conf 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that we will start with the default nginx image (&lt;strong&gt;nginx:latest&lt;/strong&gt;), but then, we will use our own configuration we have just saved in &lt;strong&gt;default.conf&lt;/strong&gt; and copy it to the location of the original configuration. Now we need to tell Docker to use our own Dockerfile instead of downloading the original image and since Dockerfile is inside the nginx directory, we need to point to that directory. So instead of using &lt;strong&gt;image: nginx:latest&lt;/strong&gt;, we will use &lt;strong&gt;build: ./nginx/&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will also create volumes so the Nginx web server and PHP as well can see the content of our &lt;strong&gt;www/html/&lt;/strong&gt; directory we have created earlier, namely our &lt;strong&gt;index.php&lt;/strong&gt; file which sits inside. This content will be in sync with the container’s directory &lt;strong&gt;/var/www/html/&lt;/strong&gt; and what’s more important, it will be persistent even when we decide to destroy containers.&lt;/p&gt;

&lt;p&gt;Next, we will create a new &lt;strong&gt;php-container&lt;/strong&gt; using original PHP image, this time PHP 7 FPM version. We need to expose &lt;strong&gt;port 9000&lt;/strong&gt; we set in &lt;strong&gt;default.conf&lt;/strong&gt; file because the original image doesn’t expose it by default. And finally, we need to link our &lt;strong&gt;nginx-container&lt;/strong&gt; to &lt;strong&gt;php-container&lt;/strong&gt;. After implementing all those changes, our modified &lt;strong&gt;docker-compose.yml&lt;/strong&gt; will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     nginx:    
       build: ./nginx/  
       container_name: nginx-container  
       ports:  
         - 80:80  
       links:  
         - php  
       volumes:  
         - ./www/html/:/var/www/html/  

    php:    
      image: php:7.0-fpm  
      container_name: php-container  
      expose:  
        - 9000  
      volumes:  
        - ./www/html/:/var/www/html/ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;NOTE: You might wonder what is the difference between&lt;/em&gt; &lt;strong&gt;&lt;em&gt;ports&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;and&lt;/em&gt; &lt;strong&gt;&lt;em&gt;expose&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;. Exposed ports are accessible only by the container to which they were exposed, in our case&lt;/em&gt; &lt;strong&gt;&lt;em&gt;php-container&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;will expose port 9000 only to the linked container which happens to be&lt;/em&gt; &lt;strong&gt;&lt;em&gt;nginx-container&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;. Ports defined just as ports are accessible by&lt;/em&gt; &lt;strong&gt;&lt;em&gt;the host machine&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;, so in my case, it would be my MacBook or rather the web browser I will use to access those ports.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Even though our &lt;strong&gt;nginx-container&lt;/strong&gt; is still running, we can run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker-compose up -d 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, Docker will pull &lt;strong&gt;php:7.0-fpm&lt;/strong&gt; image from Docker Hub and create a new image based on the instructions in our Dockerfile.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A3lKirU9-ZaZrU-yH.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A3lKirU9-ZaZrU-yH.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, Docker is warning us that it built the image for nginx service only because it didn’t exist. This means that if this image already existed, Docker wouldn’t build it and it would use the existing image instead. This is very important because even though you will change Dockerfile in the future, Docker will ignore those changes unless you specifically say that you want to rebuild the existing image by using the command &lt;strong&gt;docker-compose build&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Go ahead and take a look at the list of all images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker image ls 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the official nginx image, official php image that has just been pulled and finally, our modified version of the official nginx image which name is &lt;strong&gt;dockernginxphp7_nginx&lt;/strong&gt;. This name is based on the name of the directory where our &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file is saved. The last part of its name comes from the name of the image from which our image is derived, in our case &lt;strong&gt;_nginx&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AYADB-J_3TjiSgj3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AYADB-J_3TjiSgj3d.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;docker ps&lt;/strong&gt; will show you two containers running:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AAJIqdzngO0UgZCU1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AAJIqdzngO0UgZCU1.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: If your&lt;/em&gt; &lt;strong&gt;&lt;em&gt;nginx-container&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;is not running, use&lt;/em&gt; &lt;strong&gt;&lt;em&gt;docker logs nginx-container&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;command to see what is the problem. Very probably, it will be some kind of typo in&lt;/em&gt; &lt;strong&gt;&lt;em&gt;default.conf&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;file.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Even though we didn’t stop the original &lt;strong&gt;nginx-container&lt;/strong&gt; based on the official nginx image, it’s not only stopped, it’s completely gone. Instead, we have our new modified nginx-container running, but this one is spun up from &lt;strong&gt;dockernginxphp7_nginx&lt;/strong&gt; image. If you go back to your web browser and refresh the page, you should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AUbaubdRPURsGx_RY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AUbaubdRPURsGx_RY.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s see if the mounted directory works as expected. Go to your &lt;strong&gt;index.php&lt;/strong&gt; file and write &lt;strong&gt;AMAZING!&lt;/strong&gt; inside the h1 tag like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ActuZRwbCHFDPh2ix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ActuZRwbCHFDPh2ix.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you refresh the page, &lt;strong&gt;AMAZING!&lt;/strong&gt; will appear:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ADInNUzCXOV3ZHEUP.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ADInNUzCXOV3ZHEUP.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One last thing before we move to the database. As you might have noticed, we have mounted the same directory &lt;strong&gt;www/html/&lt;/strong&gt; to both our containers, &lt;strong&gt;nginx-container&lt;/strong&gt; and &lt;strong&gt;php-container&lt;/strong&gt;. While this is perfectly legit, it is a common practice to have a special data container for this purpose. Data container holds data and all other containers are connected or linked to it.&lt;/p&gt;

&lt;p&gt;In order to set this up, we need to change our &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file once again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     nginx:    
      build: ./nginx/  
      container_name: nginx-container  
      ports:  
       - 80:80  
      links:  
       - php  
      volumes_from:  
       - app-data  

     php:    
      image: php:7.0-fpm  
      container_name: php-container  
      expose:  
       - 9000  
      volumes_from:  
       - app-data  

     app-data:    
      image: php:7.0-fpm  
      container_name: app-data-container  
      volumes:  
       - ./www/html/:/var/www/html/  
      command: “true” 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we added a new container, &lt;strong&gt;app-data-container&lt;/strong&gt;, which uses the parameters of the same volumes we used for &lt;strong&gt;php-container&lt;/strong&gt; and &lt;strong&gt;nginx-container&lt;/strong&gt; so far. This data container will hold the application code, so it doesn’t need to run. It only needs to exist to be accessible, but since it won’t serve any other purpose, there is no need to keep it running and thus wasting resources.&lt;/p&gt;

&lt;p&gt;We use the same official image we already have pulled previously. Again, this is to save some disk space. We don’t need to pull any new image for this purpose, the php image will work just fine. Also, we told Docker to mount volumes from &lt;strong&gt;app-data&lt;/strong&gt; for &lt;strong&gt;nginx-container&lt;/strong&gt; and &lt;strong&gt;php-container&lt;/strong&gt;, so we won’t need volumes options for those anymore and we can delete it.&lt;/p&gt;

&lt;p&gt;Finally, we say that both &lt;strong&gt;nginx-container&lt;/strong&gt; and &lt;strong&gt;php-container&lt;/strong&gt; will use volumes from &lt;strong&gt;app-data-container&lt;/strong&gt;. Run &lt;strong&gt;docker-compose up -d&lt;/strong&gt; once again. As you can see in the terminal, Docker has just created a new &lt;strong&gt;app-data-container&lt;/strong&gt; and re-created &lt;strong&gt;php-container&lt;/strong&gt; and &lt;strong&gt;nginx-container&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A-Nk-Yg3tiR-E2ION.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A-Nk-Yg3tiR-E2ION.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s see the list of containers, but this time, let’s display all containers, not just the those that are running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker ps -a 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AwLosT2--Mug0cU9B.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AwLosT2--Mug0cU9B.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the &lt;strong&gt;app-data-container&lt;/strong&gt; has been created but it’s not running because there is no reason for it to run. It only holds data. And it has been created from the same image as &lt;strong&gt;php-container&lt;/strong&gt;, so we saved hundreds of megabytes we would otherwise need if we pulled data-only container.&lt;/p&gt;

&lt;h3&gt;
  
  
  MySQL
&lt;/h3&gt;

&lt;p&gt;We need to modify our php image because we need to install the extension that will allow php to connect to mysql. To do so, we will create a new folder named &lt;strong&gt;php&lt;/strong&gt; and inside we will create a new Dockerfile with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    FROM php:7.0-fpm  
    RUN docker-php-ext-install pdo_mysql 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your folder structure should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A_7Iwd0Q3jBzZVIWE.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A_7Iwd0Q3jBzZVIWE.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to change our &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file again. We will change the way the &lt;strong&gt;php-container&lt;/strong&gt; is built, next we will add &lt;strong&gt;mysql-container&lt;/strong&gt; and &lt;strong&gt;mysql-data-container&lt;/strong&gt; and finally, we will link &lt;strong&gt;php-container&lt;/strong&gt; to &lt;strong&gt;mysql-container&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will also define some environment variables for &lt;strong&gt;mysql-container&lt;/strong&gt;. &lt;strong&gt;MYSQL_ROOT_PASSWORD&lt;/strong&gt; and &lt;strong&gt;MYSQL_DATABASE&lt;/strong&gt; variables will be applied only if a volume doesn’t contain any data. Otherwise, these will be ignored. It makes sense because otherwise, we would create a new database with the same name and root password each time we would spin up a container, thus overwriting our database content. Not the behavior we want. I will name my database &lt;strong&gt;zavrel_db&lt;/strong&gt; but go ahead and change the name if you feel like it!&lt;/p&gt;

&lt;p&gt;As with &lt;strong&gt;app-data-container&lt;/strong&gt;, &lt;strong&gt;mysql-data-container&lt;/strong&gt; will just hold the data, this time not our application code, though, but database data like tables with rows and their content. Since we won’t access this data directly, we don’t really care where they will be located on our host machine, so we don’t need to mount them to our directory structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     nginx:    
      build: ./nginx/  
      container_name: nginx-container  
      ports:  
       - 80:80  
      links:  
       - php  
      volumes_from:  
       - app-data  

     php:    
      build: ./php/  
      container_name: php-container  
      expose:  
       - 9000  
      links:  
       - mysql  
      volumes_from:  
       - app-data  

     app-data:    
      image: php:7.0-fpm  
      container_name: app-data-container  
      volumes:  
       - ./www/html/:/var/www/html/  
      command: “true”  

     mysql:    
      image: mysql:latest  
      container_name: mysql-container  
      volumes_from:  
       - mysql-data  
      environment:  
       MYSQL_ROOT_PASSWORD: secret  
       MYSQL_DATABASE: zavrel_db  
       MYSQL_USER: user  
       MYSQL_PASSWORD: password  

     mysql-data:    
      image: mysql:latest  
      container_name: mysql-data-container  
      volumes:  
       - /var/lib/mysql  
      command: "true" 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test our MySQL setup, we will modify our &lt;strong&gt;index.php&lt;/strong&gt; as well, so we can try to access our database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;     &lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;  
     &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;  
      &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Hello World!&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;  
     &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;  

     &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;  
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello World!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;  
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php echo 'We are running PHP, version: ' . phpversion(); ?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;  
      &lt;span class="cp"&gt;&amp;lt;?  
       $database ="zavrel_db";  
       $user = "user";  
       $password = "password";  
       $host = "mysql";  

       $connection = new PDO("mysql:host={$host};dbname={$database};charset=utf8", $user, $password);  
       $query = $connection-&amp;gt;query("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_TYPE='BASE TABLE'");  
       $tables = $query-&amp;gt;fetchAll(PDO::FETCH_COLUMN);  

       if (empty($tables)) {  
        echo "&amp;lt;p&amp;gt;There are no tables in database "{$database}".&amp;lt;/p&amp;gt;";  
       } else {  
        echo "&amp;lt;p&amp;gt;Database "{$database}" has the following tables:&amp;lt;/p&amp;gt;";  
        echo "&amp;lt;ul&amp;gt;";  
        foreach ($tables as $table) {  
         echo "&amp;lt;li&amp;gt;{$table}&amp;lt;/li&amp;gt;";  
        }  
        echo "&amp;lt;/ul&amp;gt;";  
       }  
      ?&amp;gt;&lt;/span&gt;  
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This new script will take values we defined for the database, user, and password (notice that these are the same as environment values we set for our &lt;strong&gt;mysql-container&lt;/strong&gt;) and try to establish the database connection. Once the connection is established, the script will try to select all tables from &lt;strong&gt;INFORMATION_SCHEMA&lt;/strong&gt; where table type is &lt;strong&gt;BASE TABLE&lt;/strong&gt;. Now, if you’re not familiar with MySQL, this might be a bit confusing for you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: Basically, every MySQL instance has a special database that stores information about all the other databases that the MySQL server maintains. This special database is called&lt;/em&gt; &lt;strong&gt;&lt;em&gt;INFORMATION_SCHEMA&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;.&lt;/em&gt; &lt;strong&gt;&lt;em&gt;INFORMATION_SCHEMA&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;database contains several read-only tables. They are actually views, not databases. Databases are of&lt;/em&gt; &lt;strong&gt;&lt;em&gt;BASE TABLE&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;type.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So when we try to select the table of type &lt;strong&gt;BASE TABLE&lt;/strong&gt; we are actually looking for a database only and it is the database we will yet have to create. If it’s too much for you, don’t worry, it will all make sense soon.&lt;/p&gt;

&lt;p&gt;Anyway, once you have &lt;strong&gt;Dockerfile&lt;/strong&gt; and &lt;strong&gt;index.php&lt;/strong&gt; updated, run &lt;strong&gt;docker-compose up -d&lt;/strong&gt; again. Docker will pull mysql image, next, it will download and install a php extension for connection to the database.&lt;/p&gt;

&lt;p&gt;Finally, it will start &lt;strong&gt;app-data-container&lt;/strong&gt;, create &lt;strong&gt;mysql-data-container&lt;/strong&gt; and &lt;strong&gt;mysql-container&lt;/strong&gt; and recreate &lt;strong&gt;php-container&lt;/strong&gt; and &lt;strong&gt;nginx-container&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AX6pt7ZF9toPl2iCs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AX6pt7ZF9toPl2iCs.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check with &lt;strong&gt;docker ps -a&lt;/strong&gt; that you have five containers now, 2 of them exited (&lt;strong&gt;mysql-data-container&lt;/strong&gt; and &lt;strong&gt;app-data-container&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ANN0xm5eNbN3MVn3I.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ANN0xm5eNbN3MVn3I.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refresh &lt;strong&gt;index.php&lt;/strong&gt; in your web browser. You should see this line at the bottom: &lt;strong&gt;There are no tables in the database “zavrel_db”.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A4aeJvOEd9lEWdaGP.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A4aeJvOEd9lEWdaGP.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which is perfectly fine because we haven’t created any tables in our database yet. However, there are already some tables, but those are not visible by a regular user. If you want to see them, change &lt;strong&gt;$user&lt;/strong&gt; to “root” and &lt;strong&gt;$password&lt;/strong&gt; to “secret” in &lt;strong&gt;index.php&lt;/strong&gt;. This way, you will get access to everything!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ASeKnA2L9RTTAZ5BO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ASeKnA2L9RTTAZ5BO.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refresh the browser once more:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A7hK4a8z9C7DFtOce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A7hK4a8z9C7DFtOce.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What a list! Right? Ok, let’s put back our regular user who can see only what he should see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A3mvU9qVXTH1ARGye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A3mvU9qVXTH1ARGye.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep down the rabbit hole
&lt;/h3&gt;

&lt;p&gt;So far, containers were like black boxes for us. We ran them, we listed them, but we never saw what is inside. That’s about to change now. I will show you how you can get right inside &lt;strong&gt;mysql-container&lt;/strong&gt; and work with mysql server from within.&lt;/p&gt;

&lt;p&gt;Run this command from your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker exec -it mysql-container /bin/bash 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you are inside the container! You can tell by the new prompt in your terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AgesDU5MEqASAKb7n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AgesDU5MEqASAKb7n.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It consists now of &lt;strong&gt;root@&lt;/strong&gt; followed by the ID of the &lt;strong&gt;mysql-container&lt;/strong&gt;. In my case, it’s &lt;strong&gt;root@8a56e15cdd4d&lt;/strong&gt;, in your case the ID would be different, but it is the same ID your &lt;strong&gt;mysql-container&lt;/strong&gt; has assigned. Want to check? List all running containers by &lt;strong&gt;docker ps&lt;/strong&gt; and look for the &lt;strong&gt;CONTAINER ID&lt;/strong&gt; in the list, it’s the first column.&lt;/p&gt;

&lt;p&gt;You can now take a look around as you would in any other Linux system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;ls&lt;/strong&gt; command will show you the list of files and directories,&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;pwd&lt;/strong&gt; command will print the current directory, which is root directory (/),&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;uname -or&lt;/strong&gt; command will show you the kernel release and that this is actually a Linux operating system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember, how we defined the volume for &lt;strong&gt;mysql-container&lt;/strong&gt; in &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     volumes:   
      - /var/lib/mysql 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s take a look at this directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     cd /var/lib/mysql 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ls&lt;/strong&gt; command will show you its content:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AC0PYfyrLvVPYY1df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AC0PYfyrLvVPYY1df.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All right, let’s end this quick trip by going back to the root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     cd / 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we will run &lt;strong&gt;mysql command-line interface&lt;/strong&gt; (MySQL CLI) inside our &lt;strong&gt;mysql-container&lt;/strong&gt; that will allow us to work with the database server.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: I want you to stop now for a while to let this sink and appreciate. You are working on your physical computer. This computer is running an operating system, Windows or Mac (if you’re on Linux, it’s a bit different). Inside your operating system, you are running a Docker container which is basically a Linux machine. &lt;br&gt;&lt;br&gt;
 Now, we will go even deeper and run another command-line interface to work with the database server. Can you see how we go deeper and deeper, layer after layer, down the rabbit hole?&lt;/em&gt; 🙂&lt;/p&gt;

&lt;p&gt;Ok, let’s go back to work! To get access to MySQL CLI, we need a username and password. Luckily for us, we already created both user and password when we set up environment variables for our &lt;strong&gt;mysql-container&lt;/strong&gt; in &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file. I hope you noticed that we also set up the root password as an environment variable. Remember this line?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     MYSQL_ROOT_PASSWORD: secret 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might ask, how do we know that there is a user named root. Well, there is always this user. That’s why we were able to set the password for him with MYSQL_ROOT_PASSWORD variable without even questioning his existence.&lt;/p&gt;

&lt;p&gt;To sign in mysql server, though, we won’t use root access because that would give us too many results as root can see everything.&lt;/p&gt;

&lt;p&gt;Sign in with a regular user instead: &lt;strong&gt;mysql -uuser -ppassword&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-uuser&lt;/strong&gt; means &lt;strong&gt;user is “user”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-ppassword&lt;/strong&gt; means &lt;strong&gt;password is “password”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run the command and you will be taken deeper, inside the world mysql server. Again, you can tell by the prompt which changed now from &lt;strong&gt;root@8a56e15cdd4d&lt;/strong&gt; (different ID in your case) to &lt;strong&gt;mysql&amp;gt;&lt;/strong&gt; that we are somewhere else.&lt;/p&gt;

&lt;p&gt;Inside mysql, there are different rules and different commands. Start with the command &lt;strong&gt;show databases;&lt;/strong&gt; Don’t forget the semicolon! I told you, there are different rules in this world.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A7m-vILkJ7SzWMqcd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A7m-vILkJ7SzWMqcd.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see the nice table with the list of all databases available. One of them is our own database with &lt;strong&gt;zavrel_db&lt;/strong&gt;. Remember when we created it? Again, we defined it while preparing our &lt;strong&gt;mysql-container&lt;/strong&gt; in &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file: &lt;strong&gt;MYSQL_DATABASE: zavrel_db&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s create a new table in our database. First, we need to select it, so mysql knows which database we want to work with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     use zavrel_db 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get the information that the database has been changed. Now, we can create a new table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     CREATE TABLE users (id int); 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to your web browser and refresh the page, you will see this table in the list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AsFW-leWvnGDwPWgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AsFW-leWvnGDwPWgb.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok. We are done here, let’s get all the way back to the familiar terminal of our computer. First, we need to leave MySQL CLI. This can be done by command &lt;strong&gt;q&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and run it! MySQL will say &lt;strong&gt;Bye&lt;/strong&gt; and you are back inside your &lt;strong&gt;mysql-container&lt;/strong&gt;. Again, you can tell by the prompt &lt;strong&gt;root@8a56e15cdd4d&lt;/strong&gt;. Let’s go one layer up. To leave &lt;strong&gt;mysql-container&lt;/strong&gt;, just use the shortcut &lt;strong&gt;CTRL + D&lt;/strong&gt; or type &lt;strong&gt;exit&lt;/strong&gt; and hit enter. See? We are finally back to our computer terminal! How was it? Did you like the trip? I hope you did!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AZzVvaJIkrcIwmxHs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AZzVvaJIkrcIwmxHs.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted to show you this rather complicated way of working with databases and tables so you can truly appreciate the web client we will learn about in a minute, but first, I want to go back to volumes once again, because we need to address few more things about them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inspecting containers
&lt;/h3&gt;

&lt;p&gt;Remember how I told you that we don’t really care about where Docker stores volumes of &lt;strong&gt;mysql-data-container&lt;/strong&gt; on our computer (host machine) because we won’t access them directly anyway? Well, if you are curious where they are nevertheless, there is a way how to find out.&lt;/p&gt;

&lt;p&gt;Run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker inspect mysql-data-container 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for the &lt;strong&gt;Mounts&lt;/strong&gt; section in the output you will get. Next to &lt;strong&gt;Source&lt;/strong&gt; attribute is the location of database data on our host machine. It should be something like &lt;strong&gt;/var/lib/docker/volumes/&lt;/strong&gt; and so on provided you are on Mac.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AcqkwO8LN5w7Hr7PM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AcqkwO8LN5w7Hr7PM.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dangling volumes
&lt;/h3&gt;

&lt;p&gt;When you create a container with mounted volumes and later destroy the container, mounted volumes won’t be destroyed with it unless you specifically say you want to destroy them as well. Such orphan volumes are called dangling volumes.&lt;/p&gt;

&lt;p&gt;So far we used a command &lt;strong&gt;docker rm container-name -f&lt;/strong&gt; to remove containers, but if you want to destroy volumes as well, you need to add another option, &lt;strong&gt;-v&lt;/strong&gt;. So it will look like this: &lt;strong&gt;docker rm -v container-name -f&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But what about containers we already destroyed so far without destroying their volumes as well? Let’s check out if there are any such volumes. First, let’s list all the volumes we have created so far:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker volume ls 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s narrow our list by adding the filter for dangling volumes only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker volume ls -qf dangling=true 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;-q&lt;/strong&gt; stand for quiet which only displays volume names&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-f&lt;/strong&gt; stands for filter&lt;/p&gt;

&lt;p&gt;It seems like we have some:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AXKp86jwL484i1JI4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AXKp86jwL484i1JI4.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To delete them, we will combine two commands here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker volume rm $(docker volume ls -qf dangling=true) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will remove all dangling volumes for us. Since Docker 1.13 you can use an easier command instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker volume prune 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will remove all volumes not used by at least one container. Now if you check volumes again, you should have only one volume left:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker volume ls 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AYoGQHg2_3bdhA2gY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AYoGQHg2_3bdhA2gY.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We reclaimed almost 500 MB of space!&lt;/p&gt;

&lt;h3&gt;
  
  
  phpMyAdmin
&lt;/h3&gt;

&lt;p&gt;Ok, let’s move on and spin up our last container. phpMyAdmin is a great tool for managing mysql databases directly from the web browser. No one will force you to stop your trips deep inside MySQL CLI if that’s what you like, but a web interface is way more convenient in my opinion. Add the following lines at the end of your &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     phpmyadmin:    
      image: phpmyadmin/phpmyadmin  
      container_name: phpmyadmin-container  
      ports:  
       - 8080:80  
      links:  
       - mysql  
      environment:  
       PMA_HOST: mysql 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By now, everything should be fairly clear. We start with the official docker image, publish container’s port 80 to port 8080 of our host machine, so we can access phpMyAdmin from the web browser. We need to use a different port, though, because port 80 is already taken by the Nginx web server. Finally, we will link this container to our &lt;strong&gt;mysql-container&lt;/strong&gt; and set an environment variable.&lt;/p&gt;

&lt;p&gt;Go ahead and run this command once again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker-compose up -d 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker will pull the phpMyAdmin image and create &lt;strong&gt;phpmyadmin-container&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Go to your web browser and type &lt;strong&gt;:8080&lt;/strong&gt; behind the IP address your Nginx is working on. In my case, it looks like this &lt;strong&gt;0.0.0.0:8080&lt;/strong&gt;, but &lt;strong&gt;localhost:8080&lt;/strong&gt; works as well.&lt;/p&gt;

&lt;p&gt;You should be presented with this login screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AIgKFbujtATWJYky2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AIgKFbujtATWJYky2.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, log in as a regular user (user/password). You’re in mysql server! Check the list of databases on the left pane and click on &lt;strong&gt;zavrel_db&lt;/strong&gt;. Can you see the table &lt;strong&gt;users&lt;/strong&gt; we have recently created inside MySQL CLI?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AgcyUkcyYCz5SgnJy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AgcyUkcyYCz5SgnJy.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give yourself a little break, maybe a cup of coffee, and let it all digest a bit. We will continue with more exciting stuff. But since now you have learned a lot! Pat yourself on your back for this!&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Volume
&lt;/h3&gt;

&lt;p&gt;Mounting a local directory to make it accessible for &lt;strong&gt;nginx-container&lt;/strong&gt; and &lt;strong&gt;php-container&lt;/strong&gt; is fine until you want to deploy your application to some remote VPS (virtual private server). In such a case, it would be great to have your code copied to a remote volume automatically. In this section, I will show you how to use GitHub for this.&lt;/p&gt;

&lt;p&gt;Let’s make a copy of our &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file and save it as &lt;strong&gt;docker-compose-github.yml&lt;/strong&gt;. We will make some changes to our &lt;strong&gt;app-data-container&lt;/strong&gt; so it won’t mount a local directory but rather get a repository from GitHub. In case you have your code on GitHub in a public repository, this will make it very easy to spin up your development environment on a remote server with the code cloned from your repository.&lt;/p&gt;

&lt;p&gt;First, we need to create a Dockerfile for &lt;strong&gt;app-data&lt;/strong&gt; image. Create a new folder called &lt;strong&gt;app-data&lt;/strong&gt; and save the Dockerfile there with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    FROM php:7.0-fpm    
    RUN apt-get update &amp;amp;&amp;amp; apt-get install -y git    
    RUN git clone [https://github.com/zavrelj/docker-tutorial/](https://github.com/zavrelj/docker-tutorial/) /var/www/html/    
    VOLUME ["/var/www/html/"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your folder structure should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8ZDK5FwMAD_Ix_uE.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8ZDK5FwMAD_Ix_uE.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, we are using already pulled official php image, but on top of that, we will update the underlying &lt;strong&gt;debian:jessie&lt;/strong&gt; Linux distro and then install &lt;strong&gt;git&lt;/strong&gt;. Next, we will clone my public repository I have created for this purpose and save it inside &lt;strong&gt;/var/www/html&lt;/strong&gt; directory inside our container. Finally, we will create a volume from this directory, so other containers, namely &lt;strong&gt;nginx-container&lt;/strong&gt; and &lt;strong&gt;php-container&lt;/strong&gt; can access it.&lt;/p&gt;

&lt;p&gt;Now, we need to change &lt;strong&gt;app-data&lt;/strong&gt; image instructions in our &lt;strong&gt;docker-compose-github.yml&lt;/strong&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     app-data:    
      build: ./app-data/  
      container_name: app-data-container  
      command: “true” 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, let’s clean up everything, so we can start with a clean slate.&lt;/p&gt;

&lt;p&gt;Stop all containers created with a &lt;strong&gt;docker-compose&lt;/strong&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker-compose stop 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove all those stopped containers including volumes that were attached to them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker-compose rm -v 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean dangling volumes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker volume prune 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to use our new &lt;strong&gt;docker-compose-github.yml&lt;/strong&gt; file, we need to tell &lt;strong&gt;docker-compose&lt;/strong&gt; about it, otherwise, it would use the default docker-compose.yml as always.&lt;/p&gt;

&lt;p&gt;Rebuild the images with the new configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker-compose -f docker-compose-github.yml build 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and spin up containers again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker-compose -f docker-compose-github.yml up -d 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to your page in the web browser and you should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Ah2gkx1mdI5kpTWH9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Ah2gkx1mdI5kpTWH9.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Digital Ocean
&lt;/h3&gt;

&lt;p&gt;Let’s provision our development environment to a remote server. Digital Ocean is a great service. If you don’t have an account yet, sign up with &lt;a href="https://m.do.co/c/e1324db90e6f" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;my referral link&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; and you will get $10 in credit!&lt;/p&gt;

&lt;p&gt;Once you’re in, create a new Droplet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AwDhZh6zEDGK6Z2p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AwDhZh6zEDGK6Z2p4.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and choose Docker from &lt;strong&gt;One-click apps&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ASQjwvNpa80s6F_nb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ASQjwvNpa80s6F_nb.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pick the smallest size available, it’s more than enough for our purposes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AWu6KAv4IzDX35Q6f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AWu6KAv4IzDX35Q6f.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since I want you to use SSH for the remote access to your Droplet, you need to set it up, unless you already have it. The whole process is quite easy. Open new terminal window and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     ssh-keygen -t rsa 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Aik-mT_ut9GpJzydO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Aik-mT_ut9GpJzydO.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you’re asked where to save the key, just hit Enter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ATuUXrZzFEvpvizvW.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2ATuUXrZzFEvpvizvW.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If some other key is already there, it will be overwritten.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A0P1TgT6FPYOBjSiS.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A0P1TgT6FPYOBjSiS.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter the password for the newly generated key (twice).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A6EDpIucwuUTOE4OP.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A6EDpIucwuUTOE4OP.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you see this, your key is ready:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Av_FCvpqLenouU__s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2Av_FCvpqLenouU__s.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run this command to display the public key, select it and use CMD + C shortcut to copy it to the clipboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     cat ~/.ssh/id_rsa.pub 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A3cxlB28RY_k30Bhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A3cxlB28RY_k30Bhq.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to Droplet setup and hit &lt;strong&gt;New SSH Key&lt;/strong&gt; button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AhYcOcidal3fYc2Ku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AhYcOcidal3fYc2Ku.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste your copied public key to the from and fill the name of your computer:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AxrKbaXJXygQUXmrz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AxrKbaXJXygQUXmrz.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure, your computer is selected for SSH access and choose a hostname. Finally, hit that green button &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2An-B3HNILFxjPw1Je.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2An-B3HNILFxjPw1Je.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once your Droplet is created, write down its IP address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transferring the project folder
&lt;/h3&gt;

&lt;p&gt;If you have followed me step by step, you should have your &lt;strong&gt;docker-nginx-php7&lt;/strong&gt; folder on your Desktop.&lt;/p&gt;

&lt;p&gt;We will copy this folder to our Droplet so we can run Docker Compose with our YML configuration file remotely from the Droplet.&lt;/p&gt;

&lt;p&gt;To copy the folder, we will use &lt;strong&gt;rsync&lt;/strong&gt; command. Make sure you write this down exactly as it is. Instead of &lt;strong&gt;IP&lt;/strong&gt;, use the actual IP address of your Droplet. We want to transfer the actual directory, not just the content inside it, so we need to omit the trailing slash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     rsync -r -e ssh ~/Desktop/docker-nginx-php7 root@IP:~/ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will ask for your SSH key password and then create a copy of &lt;strong&gt;docker-nginx-php7&lt;/strong&gt; folder inside the home folder of the user root (&lt;strong&gt;/root&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A9TItLTeJRw4VswpT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A9TItLTeJRw4VswpT.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s check if everything has been transferred. SSH into your remote server (your actual IP address instead of &lt;strong&gt;IP&lt;/strong&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     ssh root@IP 
     cd docker-nginx-php7 
     ls 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can you see your familiar directory structure including two configuration files?&lt;/p&gt;

&lt;p&gt;Nice! Everything seems to be in place!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A18zeYeIUKKGa4lIs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A18zeYeIUKKGa4lIs.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s no Docker Compose on this particular Droplet, but it’s fairly easy to install it. First, we need to install &lt;strong&gt;python-pip&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     apt-get update 
     apt-get -y install python-pip 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we can install Docker Compose via pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     pip install docker-compose 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are ready now to let Docker Compose do its magic. Let’s run our familiar command that will automate the whole process of pulling and building images, getting the code from GitHub and spinning up all containers. Since there are no images to rebuild, we can use the &lt;strong&gt;up&lt;/strong&gt; command directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     docker-compose -f docker-compose-github.yml up -d 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8iYv5G4GPKOQt_AU.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2A8iYv5G4GPKOQt_AU.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once everything is done and all containers are running, you can navigate to IP address of your Droplet (&lt;a href="http://104.236.209.37/" rel="noopener noreferrer"&gt;&lt;strong&gt;http://104.236.209.37/&lt;/strong&gt;&lt;/a&gt; in my case). Octocat should be waiting for you:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AtzW6StqGE7TMDcZ9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AtzW6StqGE7TMDcZ9.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you add port 8080 behind the IP address, you will get phpMyAdmin welcome screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AU6RtlVeGHifMnYWz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AU6RtlVeGHifMnYWz.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and login with user / password or root / secret, both will work. Make sure that our &lt;strong&gt;zavrel_db&lt;/strong&gt; database is there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AxZAL9-ptpzHDZoOp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.zavrel.net%2Fimages%2FDocker-and-Docker-Compose-for-PHP-development-with-GitHub-and-Digital-Ocean-deployment%2F0%2AxZAL9-ptpzHDZoOp.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One last thing. Once you’re done with Digital Ocean, make sure to destroy your running Droplet so you won’t be billed. Or in case you used my referral link and received those $10 in credit, to not waste it all by running the Droplet you don’t need after you finish this tutorial.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Alright! That’s all. I hope you have learned something useful today. If you liked this article, consider by complete web development course where I will show you how to use Docker in the development process of the whole discussion server based on PHP and MySQL. Learn more at &lt;a href="https://www.twdc.online" rel="noopener noreferrer"&gt;www.twdc.online&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to learn more?
&lt;/h2&gt;

&lt;p&gt;The rest of this tutorial is available for free on &lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt; as a part of the much larger and far more detailed video course. Again, if you're new to Skillshare, you'll also get premium access to more than 22.000 courses. Remember, that you won't be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Skillshare is Netflix for life-long learners.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodewithjan.com%2Fimages%2Fposts%2Ftwdc-skillshare-cover-play.png" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

</description>
      <category>docker</category>
      <category>php</category>
      <category>github</category>
      <category>digitalocean</category>
    </item>
    <item>
      <title>Don't Be Afraid of Regular Expressions</title>
      <dc:creator>HZ</dc:creator>
      <pubDate>Mon, 25 Nov 2019 17:04:30 +0000</pubDate>
      <link>https://dev.to/zavrelj/don-t-be-afraid-of-regular-expressions-6j</link>
      <guid>https://dev.to/zavrelj/don-t-be-afraid-of-regular-expressions-6j</guid>
      <description>&lt;p&gt;Regular expressions are very powerful and intimidating at the same time. Let's see why there's no reason to fear them!&lt;/p&gt;

&lt;p&gt;I know, I know. How on Earth can you not be afraid if something like this haunts you in your dreams:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]{0,}@[a-zA-Z0–9]{1,}[\w-]{1,}([.][a-zA-Z]{2,}|[.][a-zA-Z0–9]{1,}[\w-]{1,}[.][a-zA-Z]{2,})$&lt;/p&gt;

&lt;p&gt;Actually, it's pretty easy to use and modify regular expressions. All you need is to take time to understand the structure. I see a lot of people asking for the right pattern for this and for that instead of investing in themselves and learning the rules behind &lt;strong&gt;regex&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I would like to help you, so you no longer need to spend hours on StackOverflow looking for the right pattern which will finally work.&lt;/p&gt;

&lt;p&gt;I can guarantee that once you read this short article, you will understand the pattern I frightened you with at the beginning. But on top of that, you'll be able to modify it to fit your needs!&lt;/p&gt;

&lt;h2&gt;
  
  
  I will make you a full-stack web developer in just 12 hours!
&lt;/h2&gt;

&lt;p&gt;This tutorial is a part of a much larger and far more detailed online course available for free on &lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt;. If you're new to Skillshare, you'll get premium access not only to this course for free but to &lt;strong&gt;all of my other courses as well and for a good measure, to over 22.000 courses&lt;/strong&gt; currently hosted on Skillshare. &lt;/p&gt;

&lt;p&gt;You won't be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Consider it a 2 months worth of premium education for free. It's like Netflix, only for life-long learners :-)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodewithjan.com%2Fimages%2Fposts%2Ftwdc-skillshare-cover-play.png" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  E-mail
&lt;/h2&gt;

&lt;p&gt;In case you didn't recognize it, the pattern above is for matching an e-mail address. Let's take a look at how to construct it step by step.&lt;/p&gt;

&lt;p&gt;We want to make sure that the e-mail address always starts with a word character. A word character is any English letter, digit or underscore. We use the &lt;strong&gt;^&lt;/strong&gt; sign to define the position at the start of the string. We use the &lt;strong&gt;[]&lt;/strong&gt; to define the list of individual characters like &lt;strong&gt;[abcde]&lt;/strong&gt; or the range if we use the &lt;strong&gt;-&lt;/strong&gt; sign like &lt;strong&gt;[a-e]&lt;/strong&gt;. We can use the &lt;strong&gt;[a-zA-Z0–9_]&lt;/strong&gt; pattern, to include all lowercase and uppercase letters, all ten digits and the underscore, but there's a shortcut which will give us the same result:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;^[\w]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we want to make sure that there is at least one such character:&lt;/p&gt;

&lt;p&gt;^[\w]&lt;strong&gt;{1,}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we want to allow any word character or three special characters &lt;strong&gt;.+-&lt;/strong&gt; in the name. Because we should allow &lt;strong&gt;jan.zavrel&lt;/strong&gt;, &lt;strong&gt;jan-zavrel&lt;/strong&gt; and even &lt;strong&gt;jan+zavrel&lt;/strong&gt;. This way, we can be sure that the e-mail won’t start with the dot, plus or hyphen, but can contain these special characters on other than the first position:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}&lt;strong&gt;[\w.+-]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And of course, there doesn’t have to be any such character because e-mail address can have only one word character in front of the @ character. In other words, we should allow it, but not force it:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]&lt;strong&gt;{0,}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to always include the @ character which is mandatory, but there can be only one in the whole e-mail address:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]{0,}&lt;strong&gt;@&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Right behind the @ character, we want to have a domain name. Here, we can define how many characters we want as a minimum and from which range of characters. I would go for all word characters including the hyphen &lt;strong&gt;[\w-]&lt;/strong&gt; and I want at least two of them &lt;strong&gt;{2,}&lt;/strong&gt;. If you want to allow domains like &lt;strong&gt;t.co&lt;/strong&gt;, you would have to allow one character from this range &lt;strong&gt;{1,}&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]{0,}@&lt;strong&gt;[\w-]{2,}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But this would allow even domain names like &lt;strong&gt;-_-.net&lt;/strong&gt;, right? Yes, it would. And that's something we need to fix. Do you remember how we limited what can be at the very beginning of the e-mail address? Well, we can use a similar approach here as well.&lt;/p&gt;

&lt;p&gt;So we want just a word character right behind the @ character, but no hyphens, and no underscores. Unfortunately, there's no shortcut for this, so we need to specify it by three ranges &lt;strong&gt;[a-zA-Z0–9]&lt;/strong&gt; and we need at least one such character right behind the @ character &lt;strong&gt;{1,}&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]{0,}@&lt;strong&gt;[a-zA-Z0–9]{1,}&lt;/strong&gt;[\w-]{2,}&lt;/p&gt;

&lt;p&gt;But now we set that there must be at least three characters for the domain name. To return to just two, we need to fix it like this:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]{0,}@[a-zA-Z0–9]{1,}[\w-]{&lt;strong&gt;1&lt;/strong&gt;,}&lt;/p&gt;

&lt;p&gt;Next, we need to deal with two cases. Either there’s just the domain name followed by the domain extension, or there’s subdomain name followed by the domain name followed by the extension. For example, &lt;strong&gt;abc.com&lt;/strong&gt; versus &lt;strong&gt;abc.co.uk&lt;/strong&gt;. To make this work, we need to use the &lt;strong&gt;(a|b)&lt;/strong&gt; token where &lt;strong&gt;a&lt;/strong&gt; stands for the first case, &lt;strong&gt;b&lt;/strong&gt; stands for the second case and &lt;strong&gt;|&lt;/strong&gt; stands for logical OR. In the first case, we will deal with just the domain extension, but since it will always be there no matter the case, we can safely add it to both cases. Domain extension always starts with the dot &lt;strong&gt;[.]&lt;/strong&gt;, followed by letters and we will limit the number of letters to at least two &lt;strong&gt;{2}&lt;/strong&gt;. So we need to add this pattern &lt;strong&gt;[.][a-zA-Z]{2,}&lt;/strong&gt; for both cases:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]{0,}@[a-zA-Z0–9]{1,}[\w-]{1,}&lt;strong&gt;([.][a-zA-Z]{2,}|[.][a-zA-Z]{2,})&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, for the second case, we will add the domain name in front of the domain extension, thus making the original domain name a subdomain. The domain name can consist of letters including the hyphen and again, we want at least two characters here, but no hyphen or underscore should be at the beginning of the domain name:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]{0,}@[a-zA-Z0–9]{1,}[\w-]{1,}([.][a-zA-Z]{2,}|&lt;strong&gt;[.][a-zA-Z0–9]{1,}[\w-]{1,}&lt;/strong&gt;[.][a-zA-Z]{2,})&lt;/p&gt;

&lt;p&gt;Finally, we need to mark the end of the whole pattern:&lt;/p&gt;

&lt;p&gt;^[\w]{1,}[\w.+-]{0,}@[a-zA-Z0–9]{1,}[\w-]{1,}([.][a-zA-Z]{2,}|[.][a-zA-Z0–9]{1,}[\w-]{1,}[.][a-zA-Z]{2,})&lt;strong&gt;$&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go here and test if your e-mail matches the pattern: &lt;a href="https://regex101.com/r/cUuG4K/1" rel="noopener noreferrer"&gt;https://regex101.com/r/cUuG4K/1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, I'm pretty sure if you try hard enough, you will find a perfectly legit e-mail address that won't match this pattern, but that's not the point. The point is that you can alter this basic structure to pinpoint exactly the pattern you need. The power is in your hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Password
&lt;/h2&gt;

&lt;p&gt;Ok, let's try something that will be a piece of cake for you now. Let's say we need to create a pattern for password with these requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  length must be between 8 and 16 characters&lt;/li&gt;
&lt;li&gt;  must include at least one uppercase and one lowercase letter&lt;/li&gt;
&lt;li&gt;  must include one number and one special character (@, *, $ or #)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will start with a simple dot &lt;strong&gt;.&lt;/strong&gt; which matches any single character:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we will set the range of characters between 8 and 16:&lt;/p&gt;

&lt;p&gt;.&lt;strong&gt;{8,16}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we will add the so called positive lookahead &lt;strong&gt;(?=.*[a-z])&lt;/strong&gt; in front of our pattern. This checks if at least one lower case letter exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(?=.*[a-z])&lt;/strong&gt;.{8,16}&lt;/p&gt;

&lt;p&gt;Next, we need to add uppercase letter in a similar fashion. Again, we will use the positive lookahead, but this time it will look like this &lt;strong&gt;(?=.*[A-Z])&lt;/strong&gt;. Add this pattern right behind the first lookahead:&lt;/p&gt;

&lt;p&gt;(?=.&lt;em&gt;[a-z])&lt;/em&gt;&lt;em&gt;(?=.&lt;/em&gt;[A-Z])**.{8,16}&lt;/p&gt;

&lt;p&gt;Next is at least one number. For the range of digits we can use either &lt;strong&gt;[0–9]&lt;/strong&gt; pattern or &lt;strong&gt;\d&lt;/strong&gt; shortcut.  We will use the shortcut since it’s shorter (obviously :-) thus making the whole expression more readable. The whole lookahead will look like this &lt;strong&gt;(?=.*\d)&lt;/strong&gt;. So again, add this pattern right behind the last lookahead:&lt;/p&gt;

&lt;p&gt;(?=.&lt;em&gt;[a-z])(?=.&lt;/em&gt;[A-Z])&lt;strong&gt;(?=.*\d)&lt;/strong&gt;.{8,16}&lt;/p&gt;

&lt;p&gt;Finally, we need to make sure that there is at least one special character from defined group (@, *, $, #). So the last lookahead will choose from these specific characters like this &lt;strong&gt;(?=.*[@*$#])&lt;/strong&gt;. Again, add it behind the last lookahead:&lt;/p&gt;

&lt;p&gt;(?=.&lt;em&gt;[a-z])(?=.&lt;/em&gt;[A-Z])(?=.&lt;em&gt;\d)&lt;/em&gt;&lt;em&gt;(?=.&lt;/em&gt;[@&lt;em&gt;$#])&lt;/em&gt;*.{8,16}&lt;/p&gt;

&lt;p&gt;That's it! Here's the pattern: &lt;a href="https://regex101.com/r/xquX9X/3" rel="noopener noreferrer"&gt;https://regex101.com/r/xquX9X/3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, what do you think? Not that scary, right? :-)&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to learn more?
&lt;/h2&gt;

&lt;p&gt;The rest of this tutorial is available for free on &lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt; as a part of the much larger and far more detailed video course. Again, if you're new to Skillshare, you'll also get premium access to more than 22.000 courses. Remember, that you won't be charged anything for your first 2 months and you can cancel your membership whenever you want within that period. Skillshare is Netflix for life-long learners.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skl.sh/3eP78uy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcodewithjan.com%2Fimages%2Fposts%2Ftwdc-skillshare-cover-play.png" width="800" height="400"&gt;&lt;/a&gt; &lt;/p&gt;

</description>
      <category>regex</category>
    </item>
  </channel>
</rss>
