<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <channel> <title>Juan Treminio - Senior Web Developer Blog</title> <description>Juan Treminio - Dallas based senior web developer</description> <link>https://jtreminio.com/</link> <atom:link href="https://jtreminio.com/feed.xml" rel="self" type="application/rss+xml"/> <pubDate>Sun, 06 Feb 2022 12:54:06 -0600</pubDate> <lastBuildDate>Sun, 06 Feb 2022 12:54:06 -0600</lastBuildDate> <generator>Jekyll v4.1.1</generator> <item> <title>OpenAPI Tutorial Part III: Paths and Basic Request Data</title> <description>&lt;blockquote class=&quot;success&quot;&gt; &lt;p&gt;This is Part III of a multi-part series. Below are the links to other parts of this tutorial!&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href=&quot;/blog/openapi-tutorial-part-i-introduction-to-openapi/&quot;&gt;OpenAPI Tutorial Part I: Introduction to OpenAPI&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;/blog/openapi-tutorial-part-ii-common-api-example/&quot;&gt;OpenAPI Tutorial Part II: Common API Example&lt;/a&gt;&lt;/li&gt; &lt;li&gt;OpenAPI Tutorial Part III: Paths and Basic Request Data&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote class=&quot;info&quot;&gt; &lt;p&gt;The end result of this article can be found at &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/tree/part-iii&quot;&gt;jtreminio/openapi-tutorial branch “part-iii”&lt;/a&gt;. You can clone it by doing&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ git clone git@github.com:jtreminio/openapi-tutorial.git&lt;/code&gt;&lt;/p&gt; &lt;p&gt;Make sure to checkout branch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;part-iii&lt;/code&gt;.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;&lt;a href=&quot;/blog/openapi-tutorial-part-ii-common-api-example/&quot;&gt;OpenAPI Tutorial Part II: Common API Example&lt;/a&gt; introduced a basic API codebase that we will begin adding OpenAPI support to.&lt;/p&gt; &lt;p&gt;Today we will go over adding paths, and the basic types of request data.&lt;/p&gt; &lt;h2 id=&quot;is-redoc-documentation-server-running&quot;&gt;Is Redoc Documentation Server Running?&lt;/h2&gt; &lt;p&gt;Before you go any further, make sure your Redoc server is running:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./bin/docs.sh Server started: http://127.0.0.1:8080 👀 Watching ./ &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;changes... &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;adding-paths&quot;&gt;Adding Paths&lt;/h2&gt; &lt;p&gt;You will notice that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jtreminio/openapi-tutorial&lt;/code&gt; already contains some helpful comments to help developers know how each endpoint is supposed to work.&lt;/p&gt; &lt;p&gt;For example:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PetController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * POST /pet */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;petCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RequestInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;There are also endpoints using path parameters with these comments:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * PUT /pet/{id}/photo */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;petUpdatePhoto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RequestInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;These comments are strictly for human readers, they do not affect your OpenAPI definitions. I included them because I expect many of your existing APIs might contain similar data. This might be the case for other parts of the codebase - you and your team have probably left behind comments to help each other better understand what the intended purpose of an endpoint is, how it is supposed to be called, what parameters it expects.&lt;/p&gt; &lt;p&gt;The benefit of using OpenAPI is you begin codifying these random and haphazard comments into a strict definition.&lt;/p&gt; &lt;h3 id=&quot;definition-of-a-path&quot;&gt;Definition of a Path&lt;/h3&gt; &lt;p&gt;Paths are the endpoints your API makes available for users. They will contain the verb (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE&lt;/code&gt;, etc) and, if applicable, path parameters.&lt;/p&gt; &lt;p&gt;Paths combine the verb and the endpoint to create a unique definition. All this means is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /order&lt;/code&gt; is a different path than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPDATE /order&lt;/code&gt;.&lt;/p&gt; &lt;h3 id=&quot;your-first-path-definition&quot;&gt;Your First Path Definition&lt;/h3&gt; &lt;p&gt;Let’s begin with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetController::petCreate()&lt;/code&gt;, which does not have any path parameters:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * POST /pet */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;petCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RequestInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Import the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OpenApi\Annotations&lt;/code&gt; annotations namespace at the top of the class:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;declare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strict_types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PetStoreApi\Controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OpenApi\Annotations&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PetStoreApi\Model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PetStoreApi\RequestInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now, add our first path definition:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PetController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * @OA\Post(path=&quot;/pet&quot;, * summary=&quot;Add a new pet&quot;, * operationId=&quot;petCreate&quot;, * tags={&quot;Pet&quot;}, * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;petCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RequestInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Finally, generate the new definitions file:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./bin/generate.php Warning: @OA&lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;ost&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; requires at least one @OA&lt;span class=&quot;se&quot;&gt;\R&lt;/span&gt;esponse&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;etStoreApi&lt;span class=&quot;se&quot;&gt;\C&lt;/span&gt;ontroller&lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;etController-&amp;gt;petCreate&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; ./src/Controller/PetController.php on line 20 &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; ./vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php on line 27 Wrote OAS file to ./bin/../openapi.yaml Running php-cs-fixer Loaded config default from &lt;span class=&quot;s2&quot;&gt;&quot;./.php-cs-fixer.php&quot;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Using cache file &lt;span class=&quot;s2&quot;&gt;&quot;.php-cs-fixer.cache&quot;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Ignore the warning for now, &lt;a href=&quot;http://127.0.0.1:8080&quot;&gt;reload your Redoc browser window&lt;/a&gt;, and you should see the beginning of some documentation.&lt;/p&gt; &lt;table class=&quot;table img-link&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/static/post/2022-02-06-openapi-tutorial-part-iii-paths-and-basic-request-data/your-first-endpoint-definition.png&quot; alt=&quot;your-first-endpoint-definition.png&quot; /&gt;&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;text-align: center&quot;&gt;Your first endpoint definition&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;If you take a look at your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openapi.yaml&lt;/code&gt; file you should see the change in the definition:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;/pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Pet&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pet&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;operationId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;petCreate&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;break-it-down---path-annotations&quot;&gt;Break It Down - Path Annotations&lt;/h3&gt; &lt;p&gt;These four new lines of data are the core pieces of a path definition. Let’s go over each part before moving on.&lt;/p&gt; &lt;h4 id=&quot;verb--path&quot;&gt;Verb + Path&lt;/h4&gt; &lt;p&gt;In our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetController::petCreate()&lt;/code&gt; we have added the following:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PetController&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * @OA\Post(path=&quot;/pet&quot;, * summary=&quot;Add a new pet&quot;, * operationId=&quot;petCreate&quot;, * tags={&quot;Pet&quot;}, * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;petCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RequestInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This is a path definition. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; has an annotation for each major verb:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Delete()&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Get()&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Head()&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Options()&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Patch()&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Post()&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Put()&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Trace()&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;You really only need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Delete()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Get()&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Post()&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Put()&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;The type of annotation you select and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt; value will be combined in the generated definition.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Post(path=&quot;/pet&quot;)&lt;/code&gt; generates:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;/pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;While &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Get(path=&quot;/pet&quot;)&lt;/code&gt; generates:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;/pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you have two paths, one with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Post(path=&quot;/pet&quot;)&lt;/code&gt; and the other with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Get(path=&quot;/pet&quot;)&lt;/code&gt; you can clearly see how OpenAPI considers these separate definitions:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;/pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h4 id=&quot;summary--description&quot;&gt;Summary &amp;amp; Description&lt;/h4&gt; &lt;p&gt;We also define a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;summary&lt;/code&gt; value, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;summary=&quot;Add a new pet&quot;&lt;/code&gt;. In Redoc &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;summary&lt;/code&gt; is used for the left sidebar listing all endpoints available, along with the verb (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Post&lt;/code&gt;). Each documentation generator will work differently and if you are using something else you will need to experiment yourself!&lt;/p&gt; &lt;p&gt;There is also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;description&lt;/code&gt; which we have not used yet. It is similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;summary&lt;/code&gt; except longer.&lt;/p&gt; &lt;h4 id=&quot;operation-id&quot;&gt;Operation ID&lt;/h4&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;operationId&lt;/code&gt; is a unique identifier for this path, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;operationId=&quot;petCreate&quot;&lt;/code&gt;. It must be unique across your entire definitions file. The convention is up to you, some teams like to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{verb}{Path}{Action}&lt;/code&gt; like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postPetCreate&lt;/code&gt;. Others use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{action}{PathRoot}&lt;/code&gt; like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createPet&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{pathRoot}{Action}&lt;/code&gt; like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;petCreate&lt;/code&gt;. It helps group common paths together:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;petCreate&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;petGet&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;petUpdate&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;petDelete&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;petList&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;petFindBy&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;petUpdatePhoto&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;h4 id=&quot;tags&quot;&gt;Tags&lt;/h4&gt; &lt;p&gt;Finally, we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tags&lt;/code&gt; to group common functionality together, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tags={&quot;Pet&quot;}&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;In Redoc this groups paths under a dropdown. You can think of them as categories. We will eventually have “Pet”, “Customer”, and “Store” tags. One endpoint can have one or more tags.&lt;/p&gt; &lt;blockquote class=&quot;info&quot;&gt; &lt;p&gt;A note about curly braces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ }&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;doctrine/annotations&lt;/code&gt; library to handle reading annotations. Unfortunately this library does not (currently) support PHP-style array braces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[ ]&lt;/code&gt;, which means that even for true arrays we must use curly braces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{ }&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;You would normally expect to see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tags=[&quot;Pet&quot;]&lt;/code&gt; but instead we must use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tags={&quot;Pet&quot;}&lt;/code&gt;.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;Now it is time to let OpenAPI know what parameters this endpoint expects!&lt;/p&gt; &lt;h2 id=&quot;adding-a-body-definition&quot;&gt;Adding a Body Definition&lt;/h2&gt; &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /pet&lt;/code&gt; is expecting data as part of the body request, and a possible file upload.&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;parameter&lt;/th&gt; &lt;th&gt;type&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;&lt;/td&gt; &lt;td&gt;string&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;age&lt;/code&gt;&lt;/td&gt; &lt;td&gt;integer&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;&lt;/td&gt; &lt;td&gt;string &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dog&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fish&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;&lt;/td&gt; &lt;td&gt;string &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;available&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pending&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sold&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt;&lt;/td&gt; &lt;td&gt;object &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DogInfo&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CatInfo&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FishInfo&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;photo&lt;/code&gt;&lt;/td&gt; &lt;td&gt;binary (file upload)&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;This endpoint does not use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; property in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Model\Pet&lt;/code&gt; class. We will get to that later.&lt;/p&gt; &lt;h3 id=&quot;body-annotations&quot;&gt;Body Annotations&lt;/h3&gt; &lt;p&gt;You tell the annotations library this is a body schema by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Schema()&lt;/code&gt; and defining the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schema&lt;/code&gt; property:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cd&quot;&gt;/** * @OA\Schema(schema=&quot;Pet&quot;) */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Add an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Property()&lt;/code&gt; definition to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; property:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string * @OA\Property() */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; annotation library supports auto-detecting the property type by reading the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@var&lt;/code&gt; attached to each property, or detecting any default value assigned to the property. However, I find it is best to explicitly define the type:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string * @OA\Property(type=&quot;string&quot;) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now, if you generate the definitions file again (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ./bin/generate.php&lt;/code&gt;) you will see the new schema:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;schemas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;object&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Do the same for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;age&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var int * @OA\Property(type=&quot;integer&quot;) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string &quot;dog&quot;|&quot;cat&quot;|&quot;fish&quot; * @OA\Property(type=&quot;string&quot;) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string &quot;available&quot;|&quot;pending&quot;|&quot;sold&quot; * @OA\Property(type=&quot;string&quot;) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;available&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;So far the types are simple, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type=&quot;integer&quot;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type=&quot;string&quot;&lt;/code&gt;. What would you expect a file upload to be? If you guessed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type=&quot;file&quot;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type=&quot;binary&quot;&lt;/code&gt; you would be forgiven for being wrong.&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var \SplFileInfo|null * @OA\Property(type=&quot;string&quot;, * format=&quot;binary&quot;, * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$photo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;blockquote class=&quot;warning&quot;&gt; &lt;p&gt;For now, let’s skip over adding a definition for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt;. I promise we will come back to it in later parts of this series, but discriminators are a bit more advanced than what you are learning right now.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;Regenerate your definitions file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ ./bin/generate.php&lt;/code&gt;) and behold:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;schemas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;integer&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;photo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;binary&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dog&quot;|&quot;cat&quot;|&quot;fish&quot;&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;available&quot;|&quot;pending&quot;|&quot;sold&quot;&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;object&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;break-it-down---body-annotations&quot;&gt;Break It Down - Body Annotations&lt;/h3&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; is helping us out quite a bit here. We first tell it that this entire class is a schema definition:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cd&quot;&gt;/** * @OA\Schema(schema=&quot;Pet&quot;) */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;That generates this YAML:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;schemas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You can change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schema&lt;/code&gt; to any unique name you want, it is completely separate from the actual class name. We only used the same name for class and schema to make this easier to learn.&lt;/p&gt; &lt;p&gt;With the above, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; now knows that the class directly following the annotation will contain more annotations that should be added to the definition. That is why this annotation works:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string &quot;dog&quot;|&quot;cat&quot;|&quot;fish&quot; * @OA\Property(type=&quot;string&quot;) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Property()&lt;/code&gt; annotations works in a similar way to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Schema()&lt;/code&gt; annotations - it is attached to the property directly following the annotation. So, the following would not affect &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$info&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string &quot;dog&quot;|&quot;cat&quot;|&quot;fish&quot; * @OA\Property(type=&quot;string&quot;) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var DogInfo|CatInfo|FishInfo */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You will also notice that any text added in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@var&lt;/code&gt; docblock is used as the parameter’s description. We can change this later but keep it for now.&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dog&quot;|&quot;cat&quot;|&quot;fish&quot;&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you reload your documentation page you will not actually see any of these changes! For that, we need to reference this new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pet&lt;/code&gt; schema from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /pet&lt;/code&gt; path.&lt;/p&gt; &lt;h2 id=&quot;referencing-a-schema&quot;&gt;Referencing a Schema&lt;/h2&gt; &lt;p&gt;Back in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetController::petCreate()&lt;/code&gt;, update the path annotation to:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @OA\Post(path=&quot;/pet&quot;, * summary=&quot;Add a new pet&quot;, * operationId=&quot;petCreate&quot;, * tags={&quot;Pet&quot;}, * @OA\RequestBody( * @OA\MediaType(mediaType=&quot;multipart/form-data&quot;, * @OA\Schema(ref=&quot;#/components/schemas/Pet&quot;), * ), * ), * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;petCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;RequestInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Refresh your documentation page and see the magic:&lt;/p&gt; &lt;table class=&quot;table img-link&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/static/post/2022-02-06-openapi-tutorial-part-iii-paths-and-basic-request-data/your-first-request-body.png&quot; alt=&quot;your-first-request-body.png&quot; /&gt;&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;text-align: center&quot;&gt;Your first request body&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3 id=&quot;break-it-down---requestbody-annotations&quot;&gt;Break It Down - RequestBody Annotations&lt;/h3&gt; &lt;p&gt;I just introduced two new annotations to you, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\RequestBody()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\MediaType()&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;For an endpoint that expects body data (for example, from forms) we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\RequestBody()&lt;/code&gt; annotation. This annotation should only be combined with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Post()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Put()&lt;/code&gt; (and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\Patch()&lt;/code&gt; if you ever use it).&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA\MediaType()&lt;/code&gt; defines the content-type this endpoint will accept. For any endpoint that will accept a file upload, you must use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multipart/form-data&lt;/code&gt;. You can use anything here, but should probably stick with the most common:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/json&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/xml&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text/plain; charset=utf-8&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;You can see more examples in &lt;a href=&quot;https://swagger.io/docs/specification/media-types/&quot;&gt;Swagger’s Media Type page&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;So far what we have is&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;/pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Pet&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pet&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;operationId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;petCreate&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;requestBody&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;multipart/form-data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Finally, you see the powerful &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ref&lt;/code&gt; for the first time. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ref&lt;/code&gt; deserves to have a large chunk of text explaining it, but for now let’s keep it simple.&lt;/p&gt; &lt;h4 id=&quot;at-a-glance---ref&quot;&gt;At a Glance - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ref&lt;/code&gt;&lt;/h4&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ref&lt;/code&gt; is actually &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ref&lt;/code&gt; when written to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openapi.yaml&lt;/code&gt; file. However, our annotations library expects &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ref&lt;/code&gt;. Here is what it looks like:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;/pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Pet&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;summary&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pet&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;operationId&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;petCreate&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;requestBody&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;multipart/form-data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;$ref&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;#/components/schemas/Pet&apos;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We use this special tool to reference schema defined in other parts of our definitions file. The first parts are the location OpenAPI can find the schema: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#/components/schemas&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;The last part is the actual schema name, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pet&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;This is powerful because it allows us to reuse schemas without having to copy and paste their definitions over and over.&lt;/p&gt; &lt;h2 id=&quot;enum-property-types&quot;&gt;Enum Property Types&lt;/h2&gt; &lt;p&gt;So far we have only added simple property types to our definitions, those that are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;integer&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string::binary&lt;/code&gt; (file upload).&lt;/p&gt; &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string::binary&lt;/code&gt; type clues us in that we can have further sub-types. One such example are enums.&lt;/p&gt; &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pet::$type&lt;/code&gt; property is currently defined as&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string &quot;dog&quot;|&quot;cat&quot;|&quot;fish&quot; * @OA\Property(type=&quot;string&quot;) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;However, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;dog&quot;|&quot;cat&quot;|&quot;fish&quot;&lt;/code&gt; part is only a docblock description, it does not actually apply to the OpenAPI definition (outside of description).&lt;/p&gt; &lt;p&gt;We can easily tell OpenAPI this is an enum by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string::enum&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string * @OA\Property(type=&quot;string&quot;, * enum={&quot;dog&quot;, &quot;cat&quot;, &quot;fish&quot;} * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now our definitions file knows this is an enum and will require that the string is only one of the allowed:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;schemas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dog&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fish&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;object&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Do the same for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string * @OA\Property(type=&quot;string&quot;, * enum={&quot;available&quot;, &quot;pending&quot;, &quot;sold&quot;} * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;available&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Regenerate your descriptions file:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;schemas&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dog&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fish&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;available&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pending&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sold&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;object&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now &lt;a href=&quot;http://127.0.0.1:8080&quot;&gt;reload your Redoc browser window&lt;/a&gt; and we see something useful:&lt;/p&gt; &lt;table class=&quot;table img-link&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/static/post/2022-02-06-openapi-tutorial-part-iii-paths-and-basic-request-data/enums.png&quot; alt=&quot;enums.png&quot; /&gt;&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td style=&quot;text-align: center&quot;&gt;Enums example&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3 id=&quot;but-wait-theres-more&quot;&gt;But Wait, There’s More&lt;/h3&gt; &lt;p&gt;So far we have used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; for things that we can easily do by manually-creating the definitions file. Let me show you one immediate benefit!&lt;/p&gt; &lt;p&gt;Enums are just arrays of values and are defined as you would expect:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string * @OA\Property(type=&quot;string&quot;, * enum={&quot;dog&quot;, &quot;cat&quot;, &quot;fish&quot;} * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Since the annotations live in a PHP class, we can benefit from actual PHP code in the annotations. If this enum is just an array, why not create a constant that contains the possible values?&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cd&quot;&gt;/** * @OA\Schema(schema=&quot;Pet&quot;) */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TYPE_ENUM&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;dog&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;cat&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;fish&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;STATUS_ENUM&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;available&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;pending&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sold&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string * @OA\Property(type=&quot;string&quot;, * enum=\PetStoreApi\Model\Pet::TYPE_ENUM * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * @var string * @OA\Property(type=&quot;string&quot;, * enum=\PetStoreApi\Model\Pet::STATUS_ENUM * ) */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;available&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Note that you need to use the FQDN when reference a constant in this manner.&lt;/p&gt; &lt;p&gt;This is only the most basic of what you can do with constants. As we go further along in this series we will see more useful examples!&lt;/p&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping It Up&lt;/h2&gt; &lt;p&gt;We have our first path defined! It even has some (basic) parameters, including a file upload and two enums!&lt;/p&gt; &lt;p&gt;In the next part of this series we will create more paths and their parameters, and we will go over how to add complex parameter types like arrays, objects, and arrays of objects.&lt;/p&gt; &lt;p&gt;So much left to do, but having the Redoc documentation to see our changes as we make them is a great motivator. I encourage you to continue playing with the API and reading both the &lt;a href=&quot;https://swagger.io/docs/specification/about/&quot;&gt;Swagger OpenAPI Guide&lt;/a&gt; and the &lt;a href=&quot;http://zircote.github.io/swagger-php/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt;&lt;/a&gt; documentation to further familiarize yourself with the concepts we have learned so far, and what we will learn in the upcoming parts of this series.&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; </description> <pubDate>Sun, 06 Feb 2022 00:00:00 -0600</pubDate> <link>https://jtreminio.com/blog/openapi-tutorial-part-iii-paths-and-basic-request-data/</link> <guid isPermaLink="true">https://jtreminio.com/blog/openapi-tutorial-part-iii-paths-and-basic-request-data/</guid> <category>api</category> <category>openapi</category> <category>webdev</category> <category>tutorial</category> <category>swagger</category> <category>blog</category> </item> <item> <title>OpenAPI Tutorial Part II: Common API Example</title> <description>&lt;blockquote class=&quot;success&quot;&gt; &lt;p&gt;This is Part II of a multi-part series. Below are the links to other parts of this tutorial!&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href=&quot;/blog/openapi-tutorial-part-i-introduction-to-openapi/&quot;&gt;OpenAPI Tutorial Part I: Introduction to OpenAPI&lt;/a&gt;&lt;/li&gt; &lt;li&gt;OpenAPI Tutorial Part II: Common API Example&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;/blog/openapi-tutorial-part-iii-paths-and-basic-request-data/&quot;&gt;OpenAPI Tutorial Part III: Paths and Basic Request Data&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;p&gt;In the first part of this series we&lt;/p&gt; &lt;ul&gt; &lt;li&gt;lightly touched on the differences between Swagger and OpenAPI&lt;/li&gt; &lt;li&gt;mentioned some things we can do with an OpenAPI definitions file&lt;/li&gt; &lt;li&gt;recommended tools for &lt;ul&gt; &lt;li&gt;definitions file generation&lt;/li&gt; &lt;li&gt;documentation generation&lt;/li&gt; &lt;li&gt;SDK generation&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;decided to implement an API similar to the common &lt;a href=&quot;https://petstore.swagger.io/#/&quot;&gt;Pet Store API&lt;/a&gt;&lt;/li&gt; &lt;li&gt;created annotations for generating an almost empty definitions file&lt;/li&gt; &lt;li&gt;viewed our empty documentation via Redoc&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Today, I want to begin with an already created, simple API. The purpose of this tutorial series is not to show you how to build an API from scratch, but rather to learn how you can take your existing API and use OpenAPI to make the whole process &lt;em&gt;better&lt;/em&gt;.&lt;/p&gt; &lt;h2 id=&quot;jtreminioopenapi-tutorial&quot;&gt;jtreminio/openapi-tutorial&lt;/h2&gt; &lt;p&gt;I want to introduce you to my repo, &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jtreminio/openapi-tutorial&lt;/code&gt;&lt;/a&gt;. I will use this repo to follow along with the progress made in this tutorial series.&lt;/p&gt; &lt;p&gt;This repo contains a (very) basic API example and I highly suggest you clone it locally to follow along. For this article we will begin with branch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;part-ii&lt;/code&gt;.&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git clone git@github.com:jtreminio/openapi-tutorial.git &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;openapi-tutorial &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git checkout part-ii &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Let us take a look at what is going on with this API!&lt;/p&gt; &lt;h3 id=&quot;controllers&quot;&gt;Controllers&lt;/h3&gt; &lt;p&gt;We will have &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/tree/part-ii/src/Controller&quot;&gt;three basic controllers&lt;/a&gt;, one each for managing &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Controller/PetController.php&quot;&gt;pets&lt;/a&gt;, &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Controller/CustomerController.php&quot;&gt;customers&lt;/a&gt;, and &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Controller/OrderController.php&quot;&gt;orders&lt;/a&gt;. You have probably seen code like this before, it is a basic &lt;em&gt;CRUD&lt;/em&gt; web application! Each controller will have the most common functionality:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;C&lt;/strong&gt;reate&lt;/li&gt; &lt;li&gt;&lt;strong&gt;R&lt;/strong&gt;etrieve&lt;/li&gt; &lt;li&gt;&lt;strong&gt;U&lt;/strong&gt;pdate&lt;/li&gt; &lt;li&gt;&lt;strong&gt;D&lt;/strong&gt;elete&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Controllers represent data being sent &lt;em&gt;from&lt;/em&gt; the user &lt;em&gt;to&lt;/em&gt; our API. OpenAPI calls these &lt;em&gt;requests&lt;/em&gt;.&lt;/p&gt; &lt;blockquote class=&quot;info&quot;&gt; &lt;p&gt;Please note that this is a basic implementation of &lt;em&gt;only&lt;/em&gt; the API structure. We are not going to implement database functionality like retrieving or persisting data. Anywhere that you see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;// persist $pet here&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;// find pet in DB here&lt;/code&gt; assume that we would have the logic for performing those actions there. However, for the purposes of this tutorial series we can just hand-wave those concerns away!&lt;/p&gt; &lt;/blockquote&gt; &lt;h4 id=&quot;pet-controller&quot;&gt;Pet Controller&lt;/h4&gt; &lt;p&gt;The &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Controller/PetController.php&quot;&gt;PetController&lt;/a&gt; class is used for managing pets. Our Pet Store will have an inventory of pets, and these endpoints are what will be used. Users can add new pets, update existing pets (including uploading a photo of a pet), search for pets by a unique &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; value or by some terms, and delete pets from the store.&lt;/p&gt; &lt;p&gt;Here is the list of actions and a short description for each:&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;endpoint&lt;/th&gt; &lt;th&gt;description&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /pet&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Add a new pet to the store&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /pet/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Gets a single pet by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /pet/findBy&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Finds one or more pets by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /pet/list&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Returns paginated list of pets&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT /pet/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Update a single pet by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT /pet/{id}/photo&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Update a single pet’s photo&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /pet/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Delete a single pet by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4 id=&quot;customer-controller&quot;&gt;Customer Controller&lt;/h4&gt; &lt;p&gt;The &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Controller/CustomerController.php&quot;&gt;CustomerController&lt;/a&gt; class is used for managing customers. The actions available will be very similar to those found in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetController&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;Here is the list of actions and a short description for each:&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;endpoint&lt;/th&gt; &lt;th&gt;description&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /customer&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Add a new customer to the store&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /customer/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Gets a single customer by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /customer/findBy&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Finds one or more customers by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;email_address&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;address&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phone_number&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /customer/list&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Returns paginated list of customers&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT /customer/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Update a single customer by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /customer/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Delete a single customer by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT /customer/{id}/phone&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Add a phone number to an existing customer&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /customer/{id}/phone&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Delete a phone number from an existing customer&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4 id=&quot;order-controller&quot;&gt;Order Controller&lt;/h4&gt; &lt;p&gt;The &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Controller/OrderController.php&quot;&gt;OrderController&lt;/a&gt; class is used for managing orders. The actions available will be very similar to those found in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PetController&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;Here is the list of actions and a short description for each:&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;endpoint&lt;/th&gt; &lt;th&gt;description&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /order&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Add a new order to the store&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /order/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Gets a single order by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /order/findBy&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Finds one or more orders by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customer_id&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pet_id&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /order/list&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Returns paginated list of orders&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT /order/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Update a single order by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /order/{id}&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Delete a single order by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{id}&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3 id=&quot;models&quot;&gt;Models&lt;/h3&gt; &lt;p&gt;We have three root models, and a few extra models that will be nested within the root models.&lt;/p&gt; &lt;p&gt;These models contain no logic, they are strictly for holding data in a known shape (in other words, a &lt;a href=&quot;https://dzone.com/articles/practical-php/practical-php-patterns-data&quot;&gt;DTO&lt;/a&gt;).&lt;/p&gt; &lt;h4 id=&quot;pet-model&quot;&gt;Pet Model&lt;/h4&gt; &lt;p&gt;The &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Pet.php&quot;&gt;Pet Model&lt;/a&gt; has some interesting properties:&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;property&lt;/th&gt; &lt;th&gt;description&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;photo&lt;/code&gt;&lt;/td&gt; &lt;td&gt;A photo of a pet. It is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nullable&lt;/code&gt;. The photo comes from a user upload&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;&lt;/td&gt; &lt;td&gt;An enum with an allowed value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dog&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fish&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Depending on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; value above, a different sub-class will be used. &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/DogInfo.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DogInfo&lt;/code&gt;&lt;/a&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dog&lt;/code&gt;, &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/CatInfo.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CatInfo&lt;/code&gt;&lt;/a&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt;, &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/FishInfo.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FishInfo&lt;/code&gt;&lt;/a&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fish&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Another enum with an allowed value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;available&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pending&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sold&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4 id=&quot;customer-model&quot;&gt;Customer Model&lt;/h4&gt; &lt;p&gt;The &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Customer.php&quot;&gt;Customer Model&lt;/a&gt; has some interesting properties:&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;property&lt;/th&gt; &lt;th&gt;description&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;address&lt;/code&gt;&lt;/td&gt; &lt;td&gt;Address of the customer, represented by the &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Address.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Address&lt;/code&gt;&lt;/a&gt; model&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phone_numbers&lt;/code&gt;&lt;/td&gt; &lt;td&gt;An array of &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Phone.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Phone&lt;/code&gt;&lt;/a&gt; models&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h4 id=&quot;order-model&quot;&gt;Order Model&lt;/h4&gt; &lt;p&gt;The &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Order.php&quot;&gt;Order Model&lt;/a&gt; is pretty boring, with only one note-worthy property:&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;property&lt;/th&gt; &lt;th&gt;description&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status&lt;/code&gt;&lt;/td&gt; &lt;td&gt;An enum with an allowed value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;placed&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shipped&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;delivered&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h2 id=&quot;why-this-api&quot;&gt;Why This API?&lt;/h2&gt; &lt;p&gt;So the above API is pretty boring, right? Nothing exciting about it, at all. That is the point. Start off with something you have seen a thousand times before and apply some new tooling to end up with an amazing new result.&lt;/p&gt; &lt;p&gt;Looks can be deceiving, though. Yes, this API is boring, but it actually has some very interesting properties I would like to point out.&lt;/p&gt; &lt;h3 id=&quot;verbs&quot;&gt;Verbs&lt;/h3&gt; &lt;p&gt;We saw the use of several REST verbs above:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /pet&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT /pet/{id}&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /pet/{id}&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /pet/{id}&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;There are a few more, but these four will meet almost all your needs. Their purpose is pretty clear and different from the other:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; - Create a new record, like creating a new pet&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT&lt;/code&gt; - Similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; but used to update en existing record, like changing the name of an existing pet&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt; - Get an existing record, like searching for an existing pet&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE&lt;/code&gt; - Delete an existing record, like deleting an existing pet&lt;/li&gt; &lt;/ul&gt; &lt;h3 id=&quot;enums&quot;&gt;Enums&lt;/h3&gt; &lt;p&gt;If you are a PHP developer you know that Enums are available as of version 8.1.&lt;/p&gt; &lt;p&gt;However, you can define properties as enum in OpenAPI easily. The endpoint &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /customer/findBy&lt;/code&gt; has a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt; string property that can only be one of the following values:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;email_address&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;address&lt;/code&gt;&lt;/li&gt; &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phone_number&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;We will learn how to define this using OpenAPI.&lt;/p&gt; &lt;h3 id=&quot;nested-objects&quot;&gt;Nested Objects&lt;/h3&gt; &lt;p&gt;The Customer Model’s &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Customer.php#L29-L32&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;address&lt;/code&gt;&lt;/a&gt; property is a single non-nullable instance of the &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Address.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Address&lt;/code&gt;&lt;/a&gt; model.&lt;/p&gt; &lt;h3 id=&quot;arrays&quot;&gt;Arrays&lt;/h3&gt; &lt;p&gt;The Customer Model’s &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Customer.php#L34-L37&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phone_numbers&lt;/code&gt;&lt;/a&gt; property is defined as an array of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Phone&lt;/code&gt; objects, (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Phone[]&lt;/code&gt;).&lt;/p&gt; &lt;h3 id=&quot;polymorphism&quot;&gt;Polymorphism&lt;/h3&gt; &lt;p&gt;Feels like you are back in college, right?&lt;/p&gt; &lt;p&gt;The Pet Model has a very special &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Pet.php#L34-L37&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt;&lt;/a&gt; property that can be one of three types. The value of this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt; property completely depends on the value of the &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/blob/part-ii/src/Model/Pet.php#L29-L32&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;&lt;/a&gt; property:&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;scenario&lt;/th&gt; &lt;th&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt; instance type&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type === &quot;dog&quot;&lt;/code&gt;&lt;/td&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt; is an instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DogInfo&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type === &quot;cat&quot;&lt;/code&gt;&lt;/td&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt; is an instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CatInfo&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type === &quot;fish&quot;&lt;/code&gt;&lt;/td&gt; &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt; is an instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FishInfo&lt;/code&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;In OpenAPI this is called a &lt;a href=&quot;https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/&quot;&gt;discriminator&lt;/a&gt;. Its purpose is to figure out the value of a property or properties, depending on the value of another property.&lt;/p&gt; &lt;p&gt;We will take a deeper look at discriminators as we go further along.&lt;/p&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping It Up&lt;/h2&gt; &lt;p&gt;We now have our starter API. It currently has no OpenAPI functionality, other than the setup we performed in &lt;a href=&quot;/blog/openapi-tutorial-part-i-introduction-to-openapi/&quot;&gt;OpenAPI Tutorial Part I: Introduction to OpenAPI&lt;/a&gt;. However, it provides a solid foundation that should be very familiar to most web developers, and focuses on what I believe to be the most common use-case for OpenAPI - working with an existing API codebase.&lt;/p&gt; &lt;p&gt;In the next part of this series we will begin adding OpenAPI to our API using the &lt;a href=&quot;http://zircote.github.io/swagger-php/&quot;&gt;zircote/swagger-php&lt;/a&gt; annotations library. We should begin seeing both the power of OpenAPI and the benefits of using annotations for writing our OpenAPI definitions.&lt;/p&gt; &lt;p&gt;We still have a ways to go and much to learn!&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; </description> <pubDate>Sat, 05 Feb 2022 00:00:00 -0600</pubDate> <link>https://jtreminio.com/blog/openapi-tutorial-part-ii-common-api-example/</link> <guid isPermaLink="true">https://jtreminio.com/blog/openapi-tutorial-part-ii-common-api-example/</guid> <category>api</category> <category>openapi</category> <category>webdev</category> <category>tutorial</category> <category>swagger</category> <category>blog</category> </item> <item> <title>OpenAPI Tutorial Part I: Introduction to OpenAPI</title> <description>&lt;blockquote class=&quot;success&quot;&gt; &lt;p&gt;This is Part I of a multi-part series. Below are the links to other parts of this tutorial!&lt;/p&gt; &lt;ul&gt; &lt;li&gt;OpenAPI Tutorial Part I: Introduction to OpenAPI&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;/blog/openapi-tutorial-part-ii-common-api-example/&quot;&gt;OpenAPI Tutorial Part II: Common API Example&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;/blog/openapi-tutorial-part-iii-paths-and-basic-request-data/&quot;&gt;OpenAPI Tutorial Part III: Paths and Basic Request Data&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;/blockquote&gt; &lt;blockquote class=&quot;info&quot;&gt; &lt;p&gt;The end result of this article can be found at &lt;a href=&quot;https://github.com/jtreminio/openapi-tutorial/tree/part-i&quot;&gt;jtreminio/openapi-tutorial branch “part-i”&lt;/a&gt;. You can clone it by doing&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$ git clone git@github.com:jtreminio/openapi-tutorial.git&lt;/code&gt;&lt;/p&gt; &lt;p&gt;Make sure to checkout branch &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;part-i&lt;/code&gt;.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;Stop me if one of these scenarios sounds familiar:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;You have a public API but no SDKS. You expect your clients to use cURL to use your API.&lt;/li&gt; &lt;li&gt;You have an internal API, but no documentation. Your team’s engineers add functionality to endpoints willy-nilly and now it is hard to keep track of what parameters, and the types of each parameter, each endpoint expects.&lt;/li&gt; &lt;li&gt;You have a product, but no API. You have no idea how to even create an API, documentation, or examples. How do I even REST?&lt;/li&gt; &lt;li&gt;You have a mature public API and SDKs across several different languages. &lt;ul&gt; &lt;li&gt;Nancy, who maintained the &lt;strong&gt;Ruby SDK&lt;/strong&gt;, left 2 years ago and no one else on your team knows how to code in Ruby so it is far behind the Python SDK.&lt;/li&gt; &lt;li&gt;Joe maintains the &lt;strong&gt;Python SDK&lt;/strong&gt;, but he is lazy and slow and does not add new features in a timely manner. The Python SDK has some features the Ruby SDK is missing but has not been updated in several months and now customers are opening GitHub issues demanding an update to at least apply a security patch to some random library it uses.&lt;/li&gt; &lt;li&gt;Your team is mostly a PHP team so the &lt;strong&gt;PHP SDK&lt;/strong&gt; is kind of, more or less, almost, but not quite, on par with the actual API implementation. cURL will always have access to all the latest API endpoints and parameters. Why can’t your customers just use cURL?&lt;/li&gt; &lt;li&gt;LOL there is no &lt;strong&gt;Java or .Net SDK&lt;/strong&gt;. We’re a web development company, not a Windows shop!&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt; &lt;p&gt;You can use OpenAPI to drastically reduce, and sometimes completely eliminate, the workload required for creating, maintaining, and updating your API definition, keeping public documentation on-par with your internal API representation, and generating, maintaining, and updating your SDKs.&lt;/p&gt; &lt;p&gt;From a single YAML (or JSON) file you can define cURL examples, callback events, OAuth, and so much more. To be clear, while most of the work required to get these benefits will be done for you once you have your OpenAPI definitions file ready, some (especially SDK generation) will still require fine-tuning to both get around bugs in the tooling and to fit your specific use-case.&lt;/p&gt; &lt;h2 id=&quot;openapi-i-thought-it-was-swagger&quot;&gt;OpenAPI? I Thought It Was Swagger?&lt;/h2&gt; &lt;p&gt;Swagger’s initial release was in 2011. The company that invented Swagger was eventually purchased by &lt;a href=&quot;https://smartbear.com/&quot;&gt;SmartBear Software&lt;/a&gt;. In 2016 the Swagger specification was renamed to OpenAPI Specification.&lt;/p&gt; &lt;p&gt;Now, “Swagger” refers to the tools offered by the SmartBear Software company, including:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href=&quot;https://swagger.io/swagger-editor/&quot;&gt;Swagger Editor&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://swagger.io/swagger-ui/&quot;&gt;Swagger UI&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://swagger.io/swagger-codegen/&quot;&gt;Swagger Codegen&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;They offer several more tools, and if you want to read much more details on the differences and products offered by Swagger, read their &lt;a href=&quot;https://swagger.io/blog/api-strategy/difference-between-swagger-and-openapi/&quot;&gt;What Is the Difference Between Swagger and OpenAPI?&lt;/a&gt; page.&lt;/p&gt; &lt;p&gt;“OpenAPI” refers only to the actual specification. In other words, OpenAPI is what is &lt;a href=&quot;https://github.com/OAI/OpenAPI-Specification/tree/main/versions&quot;&gt;defined in these files&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;I will refer back to Swagger’s tools only when comparing them to other tools that, in my opinion, offer more robust support for the latest OpenAPI features.&lt;/p&gt; &lt;p&gt;The distinction here is important. Swagger is a group of tools, but OpenAPI is just a spec. By itself, OpenAPI does not actually &lt;em&gt;do&lt;/em&gt; anything. For that, we must rely on the overall ecosystem built around the spec. This will become clearer as you read along.&lt;/p&gt; &lt;h2 id=&quot;what-can-i-do-with-openapi&quot;&gt;What Can I Do With OpenAPI?&lt;/h2&gt; &lt;p&gt;At the risk of being too simplistic and getting corrected by internet nerds the world over, the OpenAPI Spec (OAS) allows you to define a RESTful API in a YAML or JSON file(s), which we will call your definitions file(s).&lt;/p&gt; &lt;p&gt;You can:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;define endpoints (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /account/create&lt;/code&gt;)&lt;/li&gt; &lt;li&gt;what parameters the endpoints will accept (maybe the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /account/create&lt;/code&gt; endpoint expects an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;email_address&lt;/code&gt;)&lt;/li&gt; &lt;li&gt;responses from the API to the requester in the form of &lt;ul&gt; &lt;li&gt;HTTP codes (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTP 200 OK&lt;/code&gt;)&lt;/li&gt; &lt;li&gt;or full JSON responses&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;authorization, like Basic Auth, API keys, OAuth2, etc&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;There are a few more main points in the OAS and we will get to them in due time. The above all exist in your definitions file, but it is just a static text file. The file itself just implements the rules from the OAS. It is once you have your spec file that you can begin playing around with the tooling that has evolved alongside the OAS.&lt;/p&gt; &lt;p&gt;All of the following tools do their work from your definitions file, &lt;em&gt;that&lt;/em&gt; is where the magic of OpenAPI begins to shine.&lt;/p&gt; &lt;h3 id=&quot;documentation&quot;&gt;Documentation&lt;/h3&gt; &lt;p&gt;Several tools exist that read your definitions file and generate beautiful documentation for end-users to read.&lt;/p&gt; &lt;p&gt;The documentation generated can look different based on which generator you use. For example, some will create beautiful HTML pages you can open in your browser. Others can spit out Markdown (or other) files.&lt;/p&gt; &lt;p&gt;There are a number of &lt;a href=&quot;https://nordicapis.com/7-open-source-openapi-documentation-generators/&quot;&gt;products that you can read about here&lt;/a&gt;. Some are open-sourced, others are paid. Each one generates pages that look different from the others, and each one implements the OAS differently as well!&lt;/p&gt; &lt;h3 id=&quot;sdk-generation&quot;&gt;SDK Generation&lt;/h3&gt; &lt;p&gt;You can generate SDKs for end-users in a number of different languages. There are two major open-sourced generators:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href=&quot;https://github.com/OpenAPITools/openapi-generator&quot;&gt;openapi-generator&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://github.com/swagger-api/swagger-codegen&quot;&gt;swagger-codegen&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Additionally, I know of one major commercial product called &lt;a href=&quot;https://www.apimatic.io/&quot;&gt;APIMatic&lt;/a&gt; that can generate SDKs, documentation, and several others things. They might be a good option if you want a third-party company to handle almost everything OpenAPI-related for you, but are somewhat expensive.&lt;/p&gt; &lt;h2 id=&quot;before-getting-started-tools-to-use&quot;&gt;Before Getting Started, Tools to Use&lt;/h2&gt; &lt;p&gt;The OpenAPI tool ecosystem is large and fast paced. Because the OAS is just a file that does not do anything by itself, figuring out all the different possibilities and deciding which tools to use can be daunting.&lt;/p&gt; &lt;p&gt;Rather than showing your OpenAPI definitions code and letting you figure out what to do with it, I figure it is easier to recommend some tools you can install on your machine before we dive in.&lt;/p&gt; &lt;h3 id=&quot;generating-your-definitions-file&quot;&gt;Generating Your Definitions File&lt;/h3&gt; &lt;p&gt;You can write your definitions file by hand but I have found that to be cumbersome and slow. While the OAS allows reusing definitions so you do not have to copy/paste the same things over and over, it is fairly limited.&lt;/p&gt; &lt;p&gt;For generating your definitions file I recommend using an annotations-based tool. While this series can be considered language-agnostic, meaning whether you are strongest in PHP, Javascript, Ruby, etc, I will use a PHP-based tool called &lt;a href=&quot;http://zircote.github.io/swagger-php/&quot;&gt;zircote/swagger-php&lt;/a&gt; to help generate the OpenAPI definitions file from PHP annotations.&lt;/p&gt; &lt;p&gt;There are a number of similar projects for other languages, and the information in this series should translate easily! Here are a few:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;PHP - &lt;a href=&quot;http://zircote.github.io/swagger-php/&quot;&gt;zircote/swagger-php&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Typescript - &lt;a href=&quot;https://github.com/lukeautry/tsoa&quot;&gt;tsoa&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Python - &lt;a href=&quot;https://github.com/axnsan12/drf-yasg&quot;&gt;drf-yasg&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Ruby - &lt;a href=&quot;https://github.com/solso/source2swagger&quot;&gt;source2swagger&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://swagger.io/blog/api-development/swagger-annotation-libraries/&quot;&gt;See more languages&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;These types of annotations-based tools offer several benefits over manually maintaining your definitions file:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;They are based on your language of choice and allow you to use language features like constants, greatly reducing the need to copy/paste data where the OAS’ reusability falls short.&lt;/li&gt; &lt;li&gt;Annotations can be placed alongside the code they are tied to. If you have a class that lists all properties an endpoint expects you can add property annotations in the same place. This makes maintaining your actual implementation with your OpenAPI definitions much simpler.&lt;/li&gt; &lt;li&gt;Your IDE may assist you with auto-complete&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;To help reduce language barriers I will first show code using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; annotations and then show the result of generating the definitions so you can compare between your results and mine.&lt;/p&gt; &lt;p&gt;You can install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; via composer with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer require zircote/swagger-php&lt;/code&gt;. I will provide setup instructions later on.&lt;/p&gt; &lt;p&gt;If you are not using PHP please follow the instructions for your specific tool.&lt;/p&gt; &lt;h3 id=&quot;generating-documentation&quot;&gt;Generating Documentation&lt;/h3&gt; &lt;p&gt;For documentation generation I highly recommend using &lt;a href=&quot;https://redoc.ly/docs/redoc/quickstart/cli/&quot;&gt;redoc-cli&lt;/a&gt;. It is a CLI-based tool that reads your definitions file and starts a server on your local machine, using Redoc.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/Redocly/redoc&quot;&gt;Redoc is a free, MIT-licensed tool&lt;/a&gt;, and the company that created it also offers a &lt;a href=&quot;https://redoc.ly/reference-docs&quot;&gt;paid, commercial product called Redocly&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;I am not receiving any kickbacks for recommending Redoc, I simply have tested a complex definitions file and found that Redoc is the only one that supports all of the OAS 3.0’s features. As we come across those specific features in this tutorial series I will point them out.&lt;/p&gt; &lt;p&gt;Install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redoc-cli&lt;/code&gt; by &lt;a href=&quot;https://redoc.ly/docs/redoc/quickstart/cli/&quot;&gt;following the instructions in their quickstart guide&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;You can install it globally by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm i -g redoc-cli&lt;/code&gt;.&lt;/p&gt; &lt;h3 id=&quot;sdk-generation-1&quot;&gt;SDK Generation&lt;/h3&gt; &lt;p&gt;For SDK generation, stick with &lt;a href=&quot;https://github.com/OpenAPITools/openapi-generator&quot;&gt;openapi-generator&lt;/a&gt;. It has a large, active community and the &lt;a href=&quot;https://openapi-generator.tech/docs/generators/&quot;&gt;number of languages it supports&lt;/a&gt; is quite impressive.&lt;/p&gt; &lt;p&gt;After extensive testing I have found &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openapi-generator&lt;/code&gt; to be the least buggy and most feature-complete. &lt;em&gt;That said&lt;/em&gt;, each language that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openapi-generator&lt;/code&gt; supports is maintained by separate contributors, so code quality, features, and usability will vary greatly between them!&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://openapi-generator.tech/docs/installation/&quot;&gt;Follow their instructions to install&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;If you are on MacOS I highly recommend installing via Homebrew: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew install openapi-generator&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;If you are on Linux (or have Docker installed on Windows), use Docker.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openapi-generator&lt;/code&gt; is a Java application and its dependencies might be a bit frustrating to setup.&lt;/p&gt; &lt;h3 id=&quot;choosing-an-api-to-implement&quot;&gt;Choosing an API to Implement&lt;/h3&gt; &lt;p&gt;For this tutorial series we will create an API similar (but different) to the &lt;a href=&quot;https://petstore.swagger.io/#/&quot;&gt;Pet Store API&lt;/a&gt; from the ground up. Doing so will help make it clear how all the separate pieces fit together to create the final product. I will try to make the process mimic how a company with an existing, manually-maintained API would implement OpenAPI.&lt;/p&gt; &lt;p&gt;Additionally, we will extend this API to include some more advanced features available in OAS 3.x. Not all APIs are as simple as the default Pet Store example, so I am hoping this will turn out to be a good learning experience.&lt;/p&gt; &lt;p&gt;We will make mistakes, and implement code that may look weird or seem like alot of copy and pasting, but I want this tutorial series to seem organic. Not everyone begins with a well-made API before moving to OpenAPI, and I want to make this useful as a real-world guide.&lt;/p&gt; &lt;h2 id=&quot;initial-setup&quot;&gt;Initial Setup&lt;/h2&gt; &lt;p&gt;Assuming you have already installed the tools from above we can now create the scripts to call these tools. First, our project’s basic directory structure.&lt;/p&gt; &lt;h3 id=&quot;organizing-our-code&quot;&gt;Organizing Our Code&lt;/h3&gt; &lt;p&gt;We will create a directory to contain our executables so we do not have to memorize the commands for regenerating the definitions file, starting the documentation server, and (eventually) creating our SDKs. This will be the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin&lt;/code&gt; directory.&lt;/p&gt; &lt;p&gt;Create directories to hold our Controller and Model classes:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; ./bin ./src/Controller ./src/Model &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;tree &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; ├── bin │ └── // ... empty └── src ├── Controller │ └── // ... empty └── Model └── // ... empty &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;setting-up-zircoteswagger-php&quot;&gt;Setting Up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt;&lt;/h3&gt; &lt;p&gt;Install it with composer:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;composer require zircote/swagger-php Using version ^4.2 &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;zircote/swagger-php ./composer.json has been created Running composer update zircote/swagger-php Loading composer repositories with package information &lt;span class=&quot;c&quot;&gt;# ... snip&lt;/span&gt; Package operations: 10 installs, 0 updates, 0 removals - Installing symfony/yaml &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;v5.4.3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Extracting archive - Installing zircote/swagger-php &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;4.2.4&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: Extracting archive 1 package suggestions were added by new dependencies, use &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;composer suggest&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; to see details. Generating autoload files &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Create a file at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./bin/generate.php&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;#!/usr/bin/env php &lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;require_once&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/../vendor/autoload.php&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OpenApi\Generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Component\Yaml\Yaml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$openapi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Generator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/../src&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$yaml&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$openapi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toYaml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Yaml&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DUMP_OBJECT_AS_MAP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Yaml&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DUMP_EMPTY_ARRAY_AS_SEQUENCE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Yaml&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DUMP_MULTI_LINE_LITERAL_BLOCK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$oas_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/../openapi.yaml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;file_put_contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$oas_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$yaml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Wrote OAS file to &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$oas_file&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You can make it executable with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chmod +x ./bin/generate.php&lt;/code&gt; or run it directly with php.&lt;/p&gt; &lt;p&gt;All we have done here is told &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; (namespaced as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OpenApi&lt;/code&gt;) to scan the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./src&lt;/code&gt; directory for its annotations, then write the generated definitions to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./openapi.yaml&lt;/code&gt; file.&lt;/p&gt; &lt;p&gt;Whenever we make a change to our OpenAPI annotations we can run this script to regenerate our definitions file.&lt;/p&gt; &lt;h3 id=&quot;setting-up-redoc-cli&quot;&gt;Setting Up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redoc-cli&lt;/code&gt;&lt;/h3&gt; &lt;p&gt;Create a file at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./bin/docs.sh&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dirname&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; redoc-cli serve &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DIR&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/../openapi.yaml&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8080 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--watch&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--options&lt;/span&gt;.requiredPropsFirst&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Make it executable with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chmod +x ./bin/docs.sh&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;Once we create our initial definitions file we can run this script to keep Redoc open in a browser. Whenever we regenerate the definitions file refreshing the browser will show the latest changes.&lt;/p&gt; &lt;h3 id=&quot;create-openapi-entry-file&quot;&gt;Create OpenApi Entry File&lt;/h3&gt; &lt;p&gt;Before we can run the generator for the first time we need to create a file with the minimum annotations requirements. Create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./src/OpenApi.php&lt;/code&gt; file with the following contents:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;declare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strict_types&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;PetStoreApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OpenApi\Annotations&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;cd&quot;&gt;/** * @OA\OpenApi(openapi=&quot;3.0.3&quot;) * @OA\Info(title=&quot;Pet Store Api&quot;, * version=&quot;1.0.0&quot;, * ) * @OA\Server(url=&quot;https://petstore.example.com/&quot;) */&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OpenApi&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@OA&lt;/code&gt; annotations will be read by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zircote/swagger-php&lt;/code&gt; library when generating our definitions file.&lt;/p&gt; &lt;p&gt;If you are following along in PHP you may want to read a little of the &lt;a href=&quot;http://zircote.github.io/swagger-php/&quot;&gt;zircote/swagger-php&lt;/a&gt; documentation. If you are using another language, do the same with your tool!&lt;/p&gt; &lt;h4 id=&quot;initial-generation&quot;&gt;Initial Generation&lt;/h4&gt; &lt;p&gt;When you run the generator script it will create a file for you. Do not worry about warnings at this point, we will fill out the requirements in the next steps.&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;php ./bin/generate.php Warning: Required @OA&lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;athItem&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; not found &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; ./vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php on line 27 Wrote OAS file to ./bin/../openapi.yaml &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Take a look at the newly-created &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./openapi.yaml&lt;/code&gt; file. It should have the following contents:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;openapi&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;3.0.0&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Pet&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Store&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Api&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1.0.0&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;servers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://petstore.example.com/&apos;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now you can run the documentation script to get Redoc working!&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./bin/docs.sh Server started: http://127.0.0.1:8080 👀 Watching ./ &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;changes... &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Open &lt;a href=&quot;http://127.0.0.1:8080&quot;&gt;http://127.0.0.1:8080&lt;/a&gt; in your browser and you should see the Redoc documentation. It will be bare bones right now, but we will quickly start filling this in.&lt;/p&gt; &lt;h3 id=&quot;final-project-structure&quot;&gt;Final Project Structure&lt;/h3&gt; &lt;p&gt;As of now you should have a project structure that looks like this:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;tree &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--filesfirst&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; ├── composer.json ├── composer.lock ├── openapi.yaml ├── bin │ ├── docs.sh │ └── generate.php ├── src │ ├── OpenApi.php ├── Controller │ └── // ... empty └── Model └── // ... empty └── vendor ├── autoload.php ├── bin │ └── openapi -&amp;gt; ../zircote/swagger-php/bin/openapi └── // ... snip &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h3 id=&quot;extra-credit&quot;&gt;Extra Credit&lt;/h3&gt; &lt;p&gt;We are going to be writing a lot of annotations, and sometimes an IDE may get confused with exactly how many spaces to indent multi-nested definitions.&lt;/p&gt; &lt;p&gt;For example, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;info&lt;/code&gt; block in your definitions file can contain much more information, like so:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cd&quot;&gt;/** * @OA\OpenApi(openapi=&quot;3.0.3&quot;) * @OA\Info(title=&quot;Pet Store Api&quot;, * version=&quot;1.0.0&quot;, * @OA\License(name=&quot;MIT&quot;, * url=&quot;https://opensource.org/licenses/MIT&quot; * ), * ) */&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you care about consistent spacing the above should look like this:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cd&quot;&gt;/** * @OA\OpenApi(openapi=&quot;3.0.3&quot;) * @OA\Info(title=&quot;Pet Store Api&quot;, * version=&quot;1.0.0&quot;, * @OA\License(name=&quot;MIT&quot;, * url=&quot;https://opensource.org/licenses/MIT&quot; * ), * ) */&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;It is a small thing, but once you get 4, 5 levels deep the small differences can get annoying.&lt;/p&gt; &lt;p&gt;Let’s use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;friendsofphp/php-cs-fixer&lt;/code&gt; to automatically format our code every time we regenerate our definitions file:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;composer require &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; friendsofphp/php-cs-fixer Using version ^3.4 &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;friendsofphp/php-cs-fixer ./composer.json has been updated &lt;span class=&quot;c&quot;&gt;# ... snip&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./.php-cs-fixer.php&lt;/code&gt; file to contain the configuration options:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$finder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PhpCsFixer\Finder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/src&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PhpCsFixer\Config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setRules&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;@DoctrineAnnotation&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;@Symfony&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setFinder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$finder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setRiskyAllowed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./bin/generate.php&lt;/code&gt; file so its contents are as follows:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;#!/usr/bin/env php &lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;require_once&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/../vendor/autoload.php&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OpenApi\Generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Component\Yaml\Yaml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$openapi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Generator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/../src&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$yaml&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$openapi&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toYaml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Yaml&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DUMP_OBJECT_AS_MAP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Yaml&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DUMP_EMPTY_ARRAY_AS_SEQUENCE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Yaml&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DUMP_MULTI_LINE_LITERAL_BLOCK&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$oas_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/../openapi.yaml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;file_put_contents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$oas_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$yaml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Wrote OAS file to &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$oas_file&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Running php-cs-fixer&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;shell_exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;__DIR__&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/../vendor/bin/php-cs-fixer fix&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Your annotations will be fixed every time you generate:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./bin/generate.php Warning: Required @OA&lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;athItem&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; not found &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; ./vendor/zircote/swagger-php/src/Loggers/DefaultLogger.php on line 27 Wrote OAS file to ./bin/../openapi.yaml Running php-cs-fixer Loaded config default from &lt;span class=&quot;s2&quot;&gt;&quot;./.php-cs-fixer.php&quot;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Using cache file &lt;span class=&quot;s2&quot;&gt;&quot;.php-cs-fixer.cache&quot;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You should have ended up with this:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;tree &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--filesfirst&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; ├── .php-cs-fixer.cache ├── .php-cs-fixer.php ├── composer.json ├── composer.lock ├── openapi.yaml ├── bin │ ├── docs.sh │ └── generate.php ├── src │ ├── OpenApi.php ├── Controller │ └── // ... empty └── Model └── // ... empty └── vendor ├── autoload.php ├── bin │ ├── openapi -&amp;gt; ../zircote/swagger-php/bin/openapi │ └── php-cs-fixer -&amp;gt; ../friendsofphp/php-cs-fixer/php-cs-fixer └── // ... snip &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping It Up&lt;/h2&gt; &lt;p&gt;Today we briefly spoke about the differences between Swagger and OpenAPI. We touched on the various things you can do with an OpenAPI definitions file, and some of the tools you can use to do these things.&lt;/p&gt; &lt;p&gt;We have installed tools to generate our definitions file, run a documentation server, and (optionally) do some formatting of our annotations declarations.&lt;/p&gt; &lt;p&gt;We even generated our first definitions file (even though it is missing some required data) and saw our documentation page (even though it’s pretty blank!).&lt;/p&gt; &lt;p&gt;In the next part of this series we will start implementing actual endpoints and their parameters. Eventually we will begin adding more advanced features like authentication.&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; </description> <pubDate>Mon, 31 Jan 2022 00:00:00 -0600</pubDate> <link>https://jtreminio.com/blog/openapi-tutorial-part-i-introduction-to-openapi/</link> <guid isPermaLink="true">https://jtreminio.com/blog/openapi-tutorial-part-i-introduction-to-openapi/</guid> <category>api</category> <category>openapi</category> <category>webdev</category> <category>tutorial</category> <category>swagger</category> <category>blog</category> </item> <item> <title>All-in-One PHP-FPM + Nginx/Apache Containers</title> <description>&lt;p&gt;In a previous post, &lt;a href=&quot;/blog/docker-php-php-fpm-configuration-via-environment-variables/&quot;&gt;Docker PHP/PHP-FPM Configuration via Environment Variables&lt;/a&gt;, I described how to use environment variables to configure PHP FPM and CLI. With my method you can override one of over 650 PHP INI settings. More INI settings are but a PR away!&lt;/p&gt; &lt;p&gt;In a more recent post, &lt;a href=&quot;/blog/php-modules-toggled-via-environment-variables/&quot;&gt;PHP Modules Toggled via Environment Variables&lt;/a&gt; I extended this concept to include modules. You can now easily (and quickly!) enable over 20 common modules, including GD, Imagick, Mongodb, and more! The images I built come with over 30 modules installed, with a small selection enabled by default, and the rest toggable by simple environment variables.&lt;/p&gt; &lt;p&gt;With these two concepts combined my other post, &lt;a href=&quot;/blog/developing-at-full-speed-with-xdebug/&quot;&gt;Developing at Full Speed with Xdebug&lt;/a&gt; becomes that much more powerful, because now you can easily enable Xdebug via a very simple env var flag. A truly delay-free debug-ready development environment has never been easier to maintain.&lt;/p&gt; &lt;p&gt;However, my Xdebug post uses a method that requires two separate PHP-FPM servers, one with Xdebug enabled and another without. To run a normal PHP project you would be required to run 4 containers in total:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;2 PHP (FPM)&lt;/li&gt; &lt;li&gt;1 Nginx (Webserver)&lt;/li&gt; &lt;li&gt;1 MySQL (Database)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;While this falls inline with Docker’s vision of one-container per responsibility, it is a hassle to have to maintain separate containers for both Nginx and PHP, with the main reason being that both separate services need access to your project’s source code in order to properly work.&lt;/p&gt; &lt;p&gt;You can argue that PHP-FPM &lt;em&gt;depends&lt;/em&gt; on a webserver to actually function and this responsibility can be considered as one. Thus, one container running both Nginx (or your webserver of choice) and PHP is desirable.&lt;/p&gt; &lt;h2 id=&quot;managing-multiple-services-under-docker&quot;&gt;Managing Multiple Services Under Docker&lt;/h2&gt; &lt;p&gt;Docker containers &lt;em&gt;can&lt;/em&gt; run multiple services just fine. However, the daemon actively checks only a single main process which includes monitoring it for health (is this service still running?) and using it for stdout/stderr logging.&lt;/p&gt; &lt;p&gt;The &lt;a href=&quot;https://docs.docker.com/config/containers/multi-service_container/&quot;&gt;official Docker documentation&lt;/a&gt; recommends using either writing your own wrapper script, or using a process manager like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisord&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;You could install Supervisord. It is a great process manager! However, why go that route when you can use a Docker-ready image to build from?&lt;/p&gt; &lt;p&gt;Enter &lt;a href=&quot;http://phusion.github.io/baseimage-docker/&quot;&gt;baseimage-docker&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;This Ubuntu 18.04-based image comes preinstalled and configured with nifty tools like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runit&lt;/code&gt; for process management (Does not require crazy weird &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init.d&lt;/code&gt; config files!), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;syslog-ng&lt;/code&gt; for log management, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cron&lt;/code&gt; which is sorely-missing from most Docker images!&lt;/p&gt; &lt;p&gt;I will not go into detail about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseimage-docker&lt;/code&gt; as their documentation is top-notch. Later you will be able to see what I created using this tool as an example.&lt;/p&gt; &lt;h2 id=&quot;setting-up-nginx&quot;&gt;Setting up Nginx&lt;/h2&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/master/nginx/Dockerfile-7.2&quot;&gt;The Nginx images extend my PHP images&lt;/a&gt;. We already have a strong PHP-focused base to start from and can simply add Nginx to it.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/3e1a6c355f9462dd676628016648882a17b613b2/nginx/files/nginx&quot;&gt;The runit file is fairly simple&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;In it we spin up Nginx as most folks do: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nginx -g &quot;daemon off;&quot;&lt;/code&gt;&lt;/p&gt; &lt;p&gt;However, the &lt;a href=&quot;https://launchpad.net/~ondrej/+archive/ubuntu/nginx&quot;&gt;Nginx build I am using&lt;/a&gt; comes with support for the handy Perl module. This allows us to pass environment variables to the Nginx config files. Normally Nginx has no support for this (which is a huge gaping hole, in my opinion) but using the Perl module you get a workable implementation.&lt;/p&gt; &lt;p&gt;The one thing I have baked in to the Nginx images is passing in an Xdebug port.&lt;/p&gt; &lt;p&gt;This works nicely with my &lt;em&gt;Developing at Full Speed with Xdebug&lt;/em&gt; blog post.&lt;/p&gt; &lt;p&gt;You can setup Nginx env var as follows:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;perl_set &lt;span class=&quot;nv&quot;&gt;$phpfpm_xdebug_port&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sub { return $ENV{&quot;PHPFPM_XDEBUG_PORT&quot;}; }&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The runit file then looks like this:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;PHPFPM_XDEBUG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PHPFPM_XDEBUG&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PHPFPM_XDEBUG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,,&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;on&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PHPFPM_XDEBUG_PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;9999 &lt;span class=&quot;k&quot;&gt;else &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PHPFPM_XDEBUG_PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;9000 &lt;span class=&quot;k&quot;&gt;fi &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;nginx &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;daemon off; env PHPFPM_XDEBUG_PORT=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PHPFPM_XDEBUG_PORT&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;;&quot;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Everything hinges on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPFPM_XDEBUG&lt;/code&gt; env var. If it is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;On&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;on&lt;/code&gt; Nginx is told to use port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9999&lt;/code&gt; for Xdebug requests, otherwise to use the standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9000&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;The final piece of the Nginx puzzle is the &lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/04ce87d1fe6784768761f872e33ee6e44c0a9f5c/nginx/files/vhost.conf&quot;&gt;virtual host config&lt;/a&gt;:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;map &lt;span class=&quot;nv&quot;&gt;$cookie_XDEBUG_SESSION&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$my_fastcgi_pass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; default 127.0.0.1:9000&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; xdebug 127.0.0.1:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;phpfpm_xdebug_port&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If Nginx detects a cookie named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XDEBUG_SESSION&lt;/code&gt; it uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;${phpfpm_xdebug_port}&lt;/code&gt;, otherwise defaults to the standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9000&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;What happens when Xdebug support is &lt;em&gt;not&lt;/em&gt; requested is that both cases would be interpreted as simply&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;map &lt;span class=&quot;nv&quot;&gt;$cookie_XDEBUG_SESSION&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$my_fastcgi_pass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; default 127.0.0.1:9000&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; xdebug 127.0.0.1:9000&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This allows us to toggle Xdebug without having to actually make any file changes.&lt;/p&gt; &lt;p&gt;Likewise, if Xdebug support is enabled (via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPFPM_XDEBUG=On&lt;/code&gt; env var) then it would be interpreted as&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;map &lt;span class=&quot;nv&quot;&gt;$cookie_XDEBUG_SESSION&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$my_fastcgi_pass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; default 127.0.0.1:9000&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; xdebug 127.0.0.1:9999&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;setting-up-php-fpm&quot;&gt;Setting up PHP-FPM&lt;/h2&gt; &lt;p&gt;The &lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/04ce87d1fe6784768761f872e33ee6e44c0a9f5c/files/php-fpm&quot;&gt;PHP-FPM runit init file is already created&lt;/a&gt; at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jtreminio/php:7.2&lt;/code&gt; level. Nginx can immediately start working with this existing service and nothing else is required on your part. However, we want to also optionally add a second PHP-FPM instance with Xdebug enabled &lt;em&gt;but only when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPFPM_XDEBUG=On&lt;/code&gt;&lt;/em&gt;!&lt;/p&gt; &lt;p&gt;runit requires a bash file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+x&lt;/code&gt; flag set (making it executable). One route we could have taken was to simply mount a file volume via a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; file but unfortunately any files shared in this manner are not set as executable. This means we have to bake in the init file into the image itself. However, if we want to continue down the path of not requiring any file system changes and make everything configurable via env vars then we need to figure out a way to prevent runit from running a second PHP-FPM instance when Xdebug is not to be enabled.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/875edc1b59b4cad83e48f457d599c709b1e932dc/apache/files/fpm-xdebug&quot;&gt;The solution I implemented&lt;/a&gt; does just that.&lt;/p&gt; &lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPFPM_XDEBUG&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;On|on&lt;/code&gt; then a harmless, low-resource service is run:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;exec tail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; /var/log/fpm-xdebug-tail &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This is because runit needs to run &lt;em&gt;something&lt;/em&gt;, and simply exiting at this point would cause the daemon to try restarting the service over and over. There is no disable flag to tell runit, “Hey, don’t run this!”.&lt;/p&gt; &lt;p&gt;Otherwise, if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPFPM_XDEBUG=On&lt;/code&gt; then a second PHP-FPM service is run using&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; /usr/sbin/php-fpm &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; FPM.pid&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/var/run/php-fpm/php-fpm-xdebug.pid&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; FPM.listen&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;127.0.0.1:9999&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;...] &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We override the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pid&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;listen&lt;/code&gt; directives to prevent any collisions with the non-Xdebug PHP-FPM instance.&lt;/p&gt; &lt;h2 id=&quot;bonus-setting-up-apache&quot;&gt;Bonus: Setting up Apache&lt;/h2&gt; &lt;p&gt;Since we are using PHP-FPM the webserver engine becomes much easier to switch out. As long as it can speak TCP it is ok, which means adding Apache support is fairly simple.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/f8b499cb59882cc903cdf174f0b50bc37689ab7d/apache/Dockerfile-7.2&quot;&gt;The Dockerfile for the Apache image&lt;/a&gt; is very similar to the Nginx one.&lt;/p&gt; &lt;p&gt;Likewise, &lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/f8b499cb59882cc903cdf174f0b50bc37689ab7d/apache/files/apache&quot;&gt;the runit init file for Apache looks similar&lt;/a&gt; and &lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/875edc1b59b4cad83e48f457d599c709b1e932dc/apache/files/fpm-xdebug&quot;&gt;the runit init file for PHP-FPM is identical&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Apache has built-in support for env vars (looking at you, Nginx) so &lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/f8b499cb59882cc903cdf174f0b50bc37689ab7d/apache/files/vhost.conf&quot;&gt;only the vhost config file needs changed&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Apache has no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; directive, but does support &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;If/Else&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&amp;lt;FilesMatch &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;php$&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &amp;lt;If &lt;span class=&quot;s2&quot;&gt;&quot;%{HTTP_COOKIE} =~ /XDEBUG_SESSION/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; SetHandler proxy:fcgi://127.0.0.1:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PHPFPM_XDEBUG_PORT&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &amp;lt;/If&amp;gt; &amp;lt;Else&amp;gt; SetHandler proxy:fcgi://127.0.0.1:9000 &amp;lt;/Else&amp;gt; &amp;lt;/FilesMatch&amp;gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The same idea from Nginx applies here, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$PHPFPM_XDEBUG_PORT&lt;/code&gt; defaults to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9000&lt;/code&gt; unless &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPFPM_XDEBUG=On&lt;/code&gt; then it defaults to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9999&lt;/code&gt;.&lt;/p&gt; &lt;h2 id=&quot;try-it-out&quot;&gt;Try It Out&lt;/h2&gt; &lt;p&gt;You can try this out fairly quickly, but first some helpers.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://www.jetbrains.com/phpstorm/marklets/&quot;&gt;Grab the PhpStorm bookmarklets&lt;/a&gt;. You do not need to use PhpStorm to use these, they simply create an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XDEBUG_SESSION&lt;/code&gt; cookie in your browser. This is the cookie the webserver listens for. Again, if this cookie does not exist all traffic is routed to the non-Xdebug PHP-FPM instance, and vice-versa.&lt;/p&gt; &lt;p&gt;Next, setup Traefik. You may remember Traefik from my &lt;a href=&quot;/blog/traefik-on-docker-for-web-developers/&quot;&gt;Traefik on Docker for Web Developers&lt;/a&gt; post. It is an amazing tool that helps map domain names to containers.&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;TRAEFIK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;docker container &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--filter&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;traefik_proxy | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; traefik_proxy &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TRAEFIK&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-eq&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NETWORK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;docker network &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--filter&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;traefik_webgateway | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; traefik_webgateway &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NETWORK&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-eq&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;docker network create &lt;span class=&quot;nt&quot;&gt;--driver&lt;/span&gt; bridge traefik_webgateway &lt;span class=&quot;k&quot;&gt;fi &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; traefik_proxy &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--publish&lt;/span&gt; 80:80 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--publish&lt;/span&gt; 8080:8080 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--restart&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--volume&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--volume&lt;/span&gt; /dev/null:/traefik.toml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; traefik &lt;span class=&quot;nt&quot;&gt;--api&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--docker&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--docker&lt;/span&gt;.domain&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;docker.localhost &lt;span class=&quot;nt&quot;&gt;--logLevel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;DEBUG &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We setup Traefik via bash instead of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; file so it is not tied to a specific project (the container name would have the parent directory prepended to it otherwise).&lt;/p&gt; &lt;p&gt;Now you can run the PHP/Nginx instance using:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.2&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik_webgateway&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jtreminio/php-nginx:7.2&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.backend=php-nginx&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.docker.network=traefik_webgateway&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.frontend.rule=Host:php-nginx.localhost&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.port=8080&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./:/var/www/&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PHP.display_errors=On&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PHP.error_reporting=-1&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PHPFPM_XDEBUG=On&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose up&lt;/code&gt; and then open &lt;a href=&quot;http://php-nginx.localhost&quot;&gt;php-nginx.localhost&lt;/a&gt; in Chrome (or another browser if you have dnsmasq installed).&lt;/p&gt; &lt;p&gt;Create an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.php&lt;/code&gt; file that simply contains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;?php phpinfo();&lt;/code&gt; to test this out.&lt;/p&gt; &lt;p&gt;If you want to try with Apache, use the following:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.2&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik_webgateway&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jtreminio/php-apache:7.2&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.backend=php-apache&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.docker.network=traefik_webgateway&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.frontend.rule=Host:php-apache.localhost&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.port=8080&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./:/var/www/&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PHP.display_errors=On&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PHP.error_reporting=-1&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PHPFPM_XDEBUG=On&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose up&lt;/code&gt; and then open &lt;a href=&quot;http://php-apache.localhost&quot;&gt;php-apache.localhost&lt;/a&gt; in Chrome (or another browser if you have dnsmasq installed).&lt;/p&gt; &lt;p&gt;You can try the Xdebug feature by either commenting out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPFPM_XDEBUG=On&lt;/code&gt; or changing it to anything not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;On|on&lt;/code&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPFPM_XDEBUG=Off&lt;/code&gt;.&lt;/p&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping It Up&lt;/h2&gt; &lt;p&gt;This project is the end result of weeks/months testing different solutions, always poking and refactoring while trying to achieve a truly immutable image.&lt;/p&gt; &lt;p&gt;Not to say that this project is &lt;em&gt;done&lt;/em&gt;, but I believe it is on the right path to becoming something many developers and companies can build their Docker deployments on.&lt;/p&gt; &lt;p&gt;Not needing to manage separate containers for PHP/Nginx|Apache, enabling or disabling Xdebug and many other modules, and configuring PHP with nothing more than simple environment variables reduces complexity and speeds up development time.&lt;/p&gt; &lt;p&gt;I would love to hear your feedback on this and would be elated if you would drop me a line if you decide to implement my images into your workflow.&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; </description> <pubDate>Tue, 18 Dec 2018 00:00:00 -0600</pubDate> <link>https://jtreminio.com/blog/all-in-one-php-fpm-nginx-apache-containers/</link> <guid isPermaLink="true">https://jtreminio.com/blog/all-in-one-php-fpm-nginx-apache-containers/</guid> <category>docker</category> <category>php</category> <category>nginx</category> <category>apache</category> <category>blog</category> </item> <item> <title>PHP Modules Toggled via Environment Variables</title> <description>&lt;p&gt;In my previous post, &lt;a href=&quot;/blog/docker-php-php-fpm-configuration-via-environment-variables/&quot;&gt;Docker PHP/PHP-FPM Configuration via Environment Variables&lt;/a&gt; I introduced my new PHP image that uses environment variables to configure its INI settings.&lt;/p&gt; &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; is a beast, to be sure. &lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/master/Dockerfile-env&quot;&gt;At over 650 lines&lt;/a&gt; it documents my attempt at bringing true flexibility to an immutable PHP image.&lt;/p&gt; &lt;p&gt;With it, you can set any number of PHP INI settings without having to rebuild your image:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; PHP.error_reporting&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;error_reporting error_reporting &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;As part of my aim for immutability, I install all of the available modules in &lt;a href=&quot;https://launchpad.net/~ondrej/+archive/ubuntu/php&quot;&gt;Ondřej Surý’s great PHP PPA&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;This increases the final Docker image size by quite a bit, but in reality file size should not be in your list of top concerns. Ease of deployment, repeatability and speed of spinning up a new instance should top your list.&lt;/p&gt; &lt;p&gt;While my PHP image hit these three spot-on, it did introduce another issue: loading all the available PHP modules would slow the engine down, or introduce small incompatibilities and bugs to your code that you were not expecting.&lt;/p&gt; &lt;p&gt;If you are not doing any image work, do you really need to have the GD or Imagick modules enabled?&lt;/p&gt; &lt;p&gt;More clearly, why have the Redis or Mongodb modules enabled if you ever only work with MySQL?&lt;/p&gt; &lt;h2 id=&quot;why-not-use-environment-variables-to-load-modules&quot;&gt;Why Not Use Environment Variables To Load Modules?&lt;/h2&gt; &lt;p&gt;The php.ini file included with my PHP images looks like this:&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;allow_url_fopen&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${PHP.allow_url_fopen}&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;allow_url_include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${PHP.allow_url_include}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This works because I have pre-defined all the variables to be used as environment variables:&lt;/p&gt; &lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PHP.allow_url_fopen=1 \&lt;/span&gt; PHP.allow_url_include= \ &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If a user does not pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP.allow_url_fopen&lt;/code&gt; to the Docker image, it defaults to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP.allow_url_include&lt;/code&gt; defaults to no value.&lt;/p&gt; &lt;p&gt;I am forced to pre-define values because some INI settings will crash the PHP engine if left empty. They must have a non-empty value.&lt;/p&gt; &lt;p&gt;Likewise, PHP modules are loaded as:&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;extension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;redis.so&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Leaving this setting blank will immediately crash PHP. You cannot simply try to not load &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redis.so&lt;/code&gt; by leaving the setting as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extension=&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;If you decide to break immutability in your Docker images and delete the INI files that have these &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extension=*&lt;/code&gt; lines in them, you may run into other problems like having to set up an entry-point directive in your Dockerfile to delete these files on startup, or if you want your PHP-FPM service to have Redis disabled but may want to use it for PHP-CLI.&lt;/p&gt; &lt;p&gt;Removing the need to change the container’s filesystem was the main purpose behind going with configuration-by-environment-variables in the first place, and this implementation only accomplishes half that.&lt;/p&gt; &lt;h2 id=&quot;meet-php_ini_scan_dir&quot;&gt;Meet &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt;&lt;/h2&gt; &lt;p&gt;When compiling PHP you tell it where to find the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php.ini&lt;/code&gt; file and you can also define where it should scan for any additional INI files.&lt;/p&gt; &lt;p&gt;In Ondřej Surý’s build the additional files path is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/php/7.2/cli/conf.d&lt;/code&gt; for CLI:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;--ini&lt;/span&gt; Configuration File &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;php.ini&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Path: /etc/php/7.2/cli Loaded Configuration File: /etc/php/7.2/cli/php.ini Scan &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional .ini files &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;: /etc/php/7.2/cli/conf.d Additional .ini files parsed: /etc/php/7.2/cli/conf.d/10-mysqlnd.ini, /etc/php/7.2/cli/conf.d/10-opcache.ini, &lt;span class=&quot;c&quot;&gt;# more lines below&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;A special env var called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; can be used to override this path.&lt;/p&gt; &lt;p&gt;By setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; to empty you can disable everything:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;--ini&lt;/span&gt; Configuration File &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;php.ini&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Path: /etc/php/7.2/cli Loaded Configuration File: /etc/php/7.2/cli/php.ini Scan &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional .ini files &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;none&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Additional .ini files parsed: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;none&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;, or change the scan directory completely, whether the directory has INI files or not:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/foo/bar &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;--ini&lt;/span&gt; Configuration File &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;php.ini&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Path: /etc/php/7.2/cli Loaded Configuration File: /etc/php/7.2/cli/php.ini Scan &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional .ini files &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;: /foo/bar Additional .ini files parsed: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;none&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;, and add additional scan directories to the default:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:/foo/bar &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;--ini&lt;/span&gt; Configuration File &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;php.ini&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Path: /etc/php/7.2/cli Loaded Configuration File: /etc/php/7.2/cli/php.ini Scan &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional .ini files &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;: :/foo/bar Additional .ini files parsed: /etc/php/7.2/cli/conf.d/10-mysqlnd.ini, /etc/php/7.2/cli/conf.d/10-opcache.ini, &lt;span class=&quot;c&quot;&gt;# more lines below&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Finally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; is powerful enough that you can even define &lt;em&gt;multiple&lt;/em&gt; directories:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:/foo/bar:/baz/bam &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;--ini&lt;/span&gt; Configuration File &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;php.ini&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Path: /etc/php/7.2/cli Loaded Configuration File: /etc/php/7.2/cli/php.ini Scan &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional .ini files &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;: :/foo/bar:/baz/bam Additional .ini files parsed: /etc/php/7.2/cli/conf.d/10-mysqlnd.ini, /etc/php/7.2/cli/conf.d/10-opcache.ini, &lt;span class=&quot;c&quot;&gt;# more lines below&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;One thing to keep in mind is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; only works with directories. There is not a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_FILE&lt;/code&gt; equivalent and you cannot use it to include specific files. It will load all INI files in a defined directory.&lt;/p&gt; &lt;p&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; you can introduce incredible flexibility to your environment by selectively loading INI files.&lt;/p&gt; &lt;h2 id=&quot;loading-modules-with-php_ini_scan_dir&quot;&gt;Loading Modules with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt;&lt;/h2&gt; &lt;p&gt;Since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; loads all INI files in a directory you have to create separate directories for all your optional modules.&lt;/p&gt; &lt;p&gt;If you install the Redis PHP module, an INI file is automatically created at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/php/7.2/cli/conf.d/20-redis.ini&lt;/code&gt;. This is convenient if you want to actually enable this module, but not desired when you are creating a Docker image with flexibility in mind!&lt;/p&gt; &lt;p&gt;Simply creating another directory within &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/conf.d&lt;/code&gt; to hold the INI file will not work, since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; scans all children. For example, moving it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/php/7.2/cli/conf.d/redis/redis.ini&lt;/code&gt; will not change anything as it will still be loaded by default. You must move it outside of the scanned directory.&lt;/p&gt; &lt;p&gt;In my Docker image I chose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/php/extra-mods&lt;/code&gt; as the directory to hold all optional module’s INI files, so the Redis INI would go in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/php/extra-mods/redis/redis.ini&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;This INI only contains a single line:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /etc/php/extra-mods/redis/redis.ini &lt;span class=&quot;nv&quot;&gt;extension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;redis.so &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you do not tell PHP to load this file, Redis is not enabled, but you can now easily enable it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:/etc/php/extra-mods/redis &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;--ini&lt;/span&gt; Configuration File &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;php.ini&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Path: /etc/php/7.2/cli Loaded Configuration File: /etc/php/7.2/cli/php.ini Scan &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional .ini files &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;: :/etc/php/extra-mods/redis Additional .ini files parsed: /etc/php/7.2/cli/conf.d/10-mysqlnd.ini, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;...snip...] /etc/php/extra-mods/redis/redis.ini &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:/etc/php/extra-mods/redis &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;redis 655:Redis Support &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; enabled 656:Redis Version &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 4.1.1 667:Registered save handlers &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; files user redis rediscluster &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;My Docker image comes with many modules installed, and with this you can now enable exactly the ones that you want:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:/etc/php/extra-mods/redis:/etc/php/extra-mods/gd:/etc/php/extra-mods/xdebug:/etc/php/extra-mods/mongodb &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;--ini&lt;/span&gt; Configuration File &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;php.ini&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Path: /etc/php/7.2/cli Loaded Configuration File: /etc/php/7.2/cli/php.ini Scan &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional .ini files &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;: :/etc/php/extra-mods/redis:/etc/php/extra-mods/gd:/etc/php/extra-mods/xdebug:/etc/php/extra-mods/mongodb Additional .ini files parsed: /etc/php/7.2/cli/conf.d/10-mysqlnd.ini, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;...snip...] /etc/php/extra-mods/redis/redis.ini, /etc/php/extra-mods/gd/gd.ini, /etc/php/extra-mods/xdebug/xdebug.ini, /etc/php/extra-mods/mongodb/mongodb.ini &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This is &lt;em&gt;extremely&lt;/em&gt; powerful. Enabling modules and changing INI settings can be done solely through environment variables, without needing to rebuild your Docker image!&lt;/p&gt; &lt;p&gt;One small nitpick: this line is extremely long and unwieldy:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:/etc/php/extra-mods/redis:/etc/php/extra-mods/gd:/etc/php/extra-mods/xdebug:/etc/php/extra-mods/mongodb &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;My images conveniently symlinks the much shorter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/p&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/php/extra-mods&lt;/code&gt;, so the above now becomes:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:/p/redis:/p/gd:/p/xdebug:/p/mongodb &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;bonus-separate-fpm-and-cli-php_ini_scan_dir-values&quot;&gt;Bonus: Separate FPM and CLI &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; Values&lt;/h2&gt; &lt;p&gt;I hope you can already see the possibilities, but let me mention one that may not be immediately obvious.&lt;/p&gt; &lt;p&gt;You can define &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP_INI_SCAN_DIR&lt;/code&gt; for PHP-FPM, while also setting it to a different value for PHP-CLI.&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; testing &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;:/p/redis &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;k&quot;&gt;***&lt;/span&gt; Running /etc/my_init.d/00_regen_ssh_host_keys.sh... &lt;span class=&quot;k&quot;&gt;***&lt;/span&gt; Running /etc/my_init.d/10_syslog-ng.init... Dec 15 05:24:09 a9e40e3240c2 syslog-ng[13]: syslog-ng starting up&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;3.13.2&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;***&lt;/span&gt; Booting runit daemon... &lt;span class=&quot;k&quot;&gt;***&lt;/span&gt; Runit started as PID 22 Dec 15 05:24:10 a9e40e3240c2 cron[27]: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;CRON&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; INFO &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;pidfile fd &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 3&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; Dec 15 05:24:10 a9e40e3240c2 cron[27]: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;CRON&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; INFO &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Running @reboot &lt;span class=&quot;nb&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Dec-2018 05:24:10] NOTICE: fpm is running, pid 32 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Dec-2018 05:24:10] NOTICE: ready to handle connections &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Dec-2018 05:24:10] NOTICE: systemd monitor interval &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;to 10000ms &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;PHP-FPM is now running with Redis enabled. Running any PHP scripts within the container will also load Redis, even if not through PHP-FPM:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; testing php &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;redis 10:Scan this &lt;span class=&quot;nb&quot;&gt;dir &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;additional .ini files &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; :/p/redis 49:/p/redis/redis.ini 653:redis 655:Redis Support &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; enabled 656:Redis Version &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 4.1.1 667:Registered save handlers &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; files user redis rediscluster 877:PHP_INI_SCAN_DIR &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; :/p/redis 1485:&lt;span class=&quot;nv&quot;&gt;$_SERVER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;PHP_INI_SCAN_DIR&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; :/p/redis 2101:This program is free software&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; you can redistribute it and/or modify &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;However, you can also run PHP-CLI with Redis disabled, without having any effect on the running PHP-FPM instance:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PHP_INI_SCAN_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/etc/php/7.2/cli/conf.d &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; testing php &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;redis 2094:This program is free software&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; you can redistribute it and/or modify &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;about-the-image-size&quot;&gt;About the Image Size…&lt;/h2&gt; &lt;p&gt;Like I mentioned, my Docker images are not optimized for size. The benefits far outweigh something as cheap as file size.&lt;/p&gt; &lt;p&gt;Surprisingly, when you compare my images with the official images you will notices that there is not much difference in size!&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker image &lt;span class=&quot;nb&quot;&gt;ls &lt;/span&gt;REPOSITORY TAG IMAGE ID CREATED SIZE jtreminio/php 7.2 9f0102616848 26 hours ago 381MB php 7.2-fpm 2bd622691e6e 4 days ago 371MB &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;10MB larger size with the benefit of not needing to compile modules, nor having to maintain a separate php.ini file and instead being able to do all this through simple environment variables!&lt;/p&gt; &lt;p&gt;A worthy trade-off, in my opinion.&lt;/p&gt; &lt;h2 id=&quot;want-to-try-it-out&quot;&gt;Want to Try It Out?&lt;/h2&gt; &lt;p&gt;&lt;a href=&quot;https://cloud.docker.com/repository/docker/jtreminio/php&quot;&gt;My images are all available on the Docker hub&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;I maintain versions for PHP 7.3 - 5.6.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/jtreminio/php-docker&quot;&gt;You can see all the Dockerfiles used to generate the images in my repo&lt;/a&gt;.&lt;/p&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping It Up&lt;/h2&gt; &lt;p&gt;I would like to thank &lt;a href=&quot;https://deb.sury.org/&quot;&gt;Ondřej Surý&lt;/a&gt; for his years-long work in maintaining amazing PHP, Nginx, Apache (and more!) repos.&lt;/p&gt; &lt;p&gt;I would also like to thank &lt;a href=&quot;https://derickrethans.nl/&quot;&gt;Derick Rethans&lt;/a&gt; for letting me bounce ideas off of him. While my final solution may seem easy and obvious in hind-sight, actually arriving at this point required hours of Derick being supernaturally patient with me while I massacre his PHP language.&lt;/p&gt; &lt;p&gt;The resulting Docker images accomplish just about everything I had in mind. As I created, tinkered and optimized them through months of use at work, and asked the coworkers on my team to also use them.&lt;/p&gt; &lt;p&gt;I hope you may find them as useful and easy to use as we have, and your productivity to match!&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; </description> <pubDate>Fri, 14 Dec 2018 00:00:00 -0600</pubDate> <link>https://jtreminio.com/blog/php-modules-toggled-via-environment-variables/</link> <guid isPermaLink="true">https://jtreminio.com/blog/php-modules-toggled-via-environment-variables/</guid> <category>docker</category> <category>php</category> <category>blog</category> </item> <item> <title>Docker PHP/PHP-FPM Configuration via Environment Variables</title> <description>&lt;p&gt;&lt;em&gt;ed: If you want to run the containers right now, jump ahead to &lt;strong&gt;&lt;a href=&quot;#how-to-use-the-images&quot;&gt;How to Use the Images&lt;/a&gt;&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;For several months now I have been working on &lt;a href=&quot;https://puphpet.com&quot;&gt;PuPHPet.com’s&lt;/a&gt; replacement. It is a Docker-based GUI functionally similar to PuPHPet.&lt;/p&gt; &lt;p&gt;Docker, like Vagrant, allows sharing directories and files from the host to the container/VM. Unlike Vagrant, Docker images are easy to create, easy to share and easy to configure.&lt;/p&gt; &lt;p&gt;One of the most convenient differences is that with Docker containers you can pass flags to the container which can be used for configuration (if the image was created with this in mind).&lt;/p&gt; &lt;p&gt;For example, with the &lt;a href=&quot;https://hub.docker.com/_/mariadb/&quot;&gt;MariaDB image&lt;/a&gt; you create a new container and define the database credentials:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;dbname &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;MYSQL_USER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;dbuser &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;dbpassword &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; mariadb &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This creates a new MariaDB container with a database named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dbname&lt;/code&gt;, user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dbuser&lt;/code&gt; and password &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dbpassword&lt;/code&gt;. You do not need to create a separate configuration file, the above takes care of that for you!&lt;/p&gt; &lt;p&gt;What if we could do the same for a PHP container? Having to keep track of a separate INI file for both PHP and PHP-FPM is not nearly as smooth as how MariaDB’s image can be configured.&lt;/p&gt; &lt;h2 id=&quot;our-goals&quot;&gt;Our Goals&lt;/h2&gt; &lt;p&gt;I will talk you through the thought process required for creating a Docker image that is capable of configuring its service using environment variable flags:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;ENV_NAME&lt;span class=&quot;o&quot;&gt;}={&lt;/span&gt;ENV_VALUE&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;I will detail the gotchas you would encounter trying to create this for yourself from scratch, and the solutions I came up with and implemented.&lt;/p&gt; &lt;h2 id=&quot;quick-introduction-to-docker-images&quot;&gt;Quick Introduction to Docker Images&lt;/h2&gt; &lt;p&gt;It will definitely help if you know Docker at a usable level. If you know Docker enough to only run pre-built images then I hope this will help solidify your understanding of how Docker works.&lt;/p&gt; &lt;p&gt;That said I am not an expert in the internal workings of Docker, and I might get some things wrong.&lt;/p&gt; &lt;p&gt;Docker &lt;em&gt;images&lt;/em&gt; are blueprints for Docker &lt;em&gt;containers&lt;/em&gt;. You need an image to run one or more instances which are known as containers.&lt;/p&gt; &lt;p&gt;An image can be grabbed from the &lt;a href=&quot;https://hub.docker.com&quot;&gt;Docker Hub&lt;/a&gt; or you can easily create your own images by writing a &lt;em&gt;Dockerfile&lt;/em&gt;. This is a simple text file that contains instructions for creating your image.&lt;/p&gt; &lt;p&gt;Unless you are doing some hardcore stuff, you always start with an existing base image. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FROM ubuntu:18.04&lt;/code&gt; means we will start with an Ubuntu 18.04 image to create our final image.&lt;/p&gt; &lt;p&gt;You can have as many layers as you want, with each new image extending the parent and adding more functionality. That is exactly what we will end up doing today.&lt;/p&gt; &lt;h2 id=&quot;php-ini-with-environment-variables-env-vars&quot;&gt;PHP INI with Environment Variables (env vars)&lt;/h2&gt; &lt;p&gt;A little-known fact is that PHP’s INI file (and PHP-FPM conf, too!) can be configured normally,&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;display_errors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;error_reporting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Off date.timezone&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;UTC &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;but it can also read env vars!&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;display_errors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DISPLAY_ERRORS&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error_reporting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ERROR_REPORTING&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; date.timezone&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DATE_TIMEZONE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;PHP-FPM can also be configured this way:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;pm &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FPM_PM&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; pm.max_children &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FPM_PM_MAX_CHILDREN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; pm.start_servers &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FPM_PM_START_SERVICE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Since we can pass environment variable values when spinning up a Docker container, this can be combined to remove the need for maintaining a separate php.ini file in your project’s repo!&lt;/p&gt; &lt;h3 id=&quot;caveats&quot;&gt;Caveats&lt;/h3&gt; &lt;p&gt;A few things to keep in mind:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;You need to be explicit about which settings you want to allow env vars for. This means you can only use env vars if you have set the INI value to read from an env var (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;display_errors=${DISPLAY_ERRORS}&lt;/code&gt;)!&lt;/li&gt; &lt;li&gt;If an env var is not set, the value will be empty. This means you must either set a default value when creating the initial INI file (not always desired), or the INI value must allow empty values.&lt;/li&gt; &lt;li&gt;Bash has a default value fallback when working with variables. It looks like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FOO=${BAR:-&quot;default value&quot;}&lt;/code&gt;. While this would have been great for our use case, INI files are &lt;em&gt;not&lt;/em&gt; processed as Bash and thus the default value trick does not work here.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;The first point means you cannot use just any Docker image, it must have been built with env vars in mind from the start. A quick search on the Docker Hub does not bring up many results for this - just about all major PHP Docker images require managing the PHP INI file directly.&lt;/p&gt; &lt;p&gt;The second point hurts us a little bit because some INI settings simply cannot have empty values. For example, you cannot have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extension=&lt;/code&gt;. PHP will complain.&lt;/p&gt; &lt;p&gt;In FPM if you have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slowlog=&lt;/code&gt; FPM will crash immediately. Unfortunately some INI settings are not amenable to env vars unless you enforce a value for them.&lt;/p&gt; &lt;p&gt;The final point is a minor inconvenience. You cannot define a setting with an inline env var in the same line. You have to do this process separately.&lt;/p&gt; &lt;p&gt;This means you cannot simply have your INI file look like:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;display_errors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DISPLAY_ERRORS&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Off&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;error_reporting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ERROR_REPORTING&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; date.timezone&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DATE_TIMEZONE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;UTC&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This would have been great but since it is strictly a Bashism it does not work within the PHP INI parser.&lt;/p&gt; &lt;p&gt;This forces you to keep a separate list of env vars and their values, and a separate list of INI settings calling the env vars.&lt;/p&gt; &lt;p&gt;We will go through these points in more detail later on.&lt;/p&gt; &lt;h2 id=&quot;building-docker-image-with-env-vars&quot;&gt;Building Docker Image with Env Vars&lt;/h2&gt; &lt;p&gt;Adding an env var to a Docker image within a Dockerfile is simple enough:&lt;/p&gt; &lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ubuntu:18.04&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; allow_url_fopen=1&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now all containers that use this image and all images that extend this image, would have the env var &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allow_url_fopen&lt;/code&gt; available to them.&lt;/p&gt; &lt;p&gt;To change this value when spinning up a container you would simply do&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;allow_url_fopen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;image_name&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;and in the php.ini using&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;allow_url_fopen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;${allow_url_fopen}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;would allow us to change the setting on the fly without having to further edit a static INI file.&lt;/p&gt; &lt;p&gt;My end goal was not to have just one or two settings available for changing, I want developers to take my images and plug them into their projects and configure as much as possible only through env vars. This means I needed to add as many INI settings as I can.&lt;/p&gt; &lt;p&gt;This leads to a problem. Dockerfile cannot read and grab env vars from a separate file. &lt;em&gt;You have to define all env vars in the Dockerfile&lt;/em&gt; to be able to use them in child images or containers.&lt;/p&gt; &lt;p&gt;In a docker-compose file you can simply use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;env_file&lt;/code&gt; like so:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;my_special_service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;something&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;env_file&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;env-file.env&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you are spinning up a container from an existing image you can likewise do&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker container run &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--env-file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/env-file.env &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;..] &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;However, you cannot do the same when &lt;em&gt;building&lt;/em&gt; a new image. There is no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENV_FILE&lt;/code&gt; in a Dockerfile - you have to list each env var one by one!&lt;/p&gt; &lt;p&gt;The problem is more obvious when I tell you I identified over 650 PHP INI settings I wanted to set as env vars. Then realize I want to support all active versions of PHP (5.6, 7.0, 7.1, 7.2, 7.3) and that means each of the four Dockerfiles now need to have 650+ lines just for the env vars.&lt;/p&gt; &lt;p&gt;If Dockerfile supports an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENV_VAR&lt;/code&gt; directive this would be a single-line solution. Since it does not, I needed to come up with some other way to add these hundreds of env vars, without junking up my Dockerfiles.&lt;/p&gt; &lt;h3 id=&quot;create-a-parent-image&quot;&gt;Create a Parent Image&lt;/h3&gt; &lt;p&gt;The solution turned out to be quite simple! Since each Dockerfile extends a parent image (via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FROM&lt;/code&gt;), and everything available to that base image is available to the current Dockerfile, I created a Dockerfile that does nothing but define all the INI env vars my PHP images will be able to use.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/master/Dockerfile-env&quot;&gt;You can see it here.&lt;/a&gt;&lt;/p&gt; &lt;p&gt;For settings that have no default value, and can remain blank, I did&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;PHP.allow_url_include&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;For settings that ship with default values, I did&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;PHP.allow_url_fopen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;blockquote class=&quot;info&quot;&gt; &lt;p&gt;Notice that I namespaced all my variables. The actual variable name is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP.allow_url_fopen&lt;/code&gt; and so the INI file must reference this as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allow_url_fopen=${PHP.allow_url_fopen}&lt;/code&gt;. This simply helps avoid naming collisions. You might also know that in Bash all variable names must only contain alphanumeric or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_&lt;/code&gt; characters. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP.allow_url_fopen&lt;/code&gt; makes this an invalid variable name in Bash.&lt;/p&gt; &lt;p&gt;Thankfully we are not reading these values in Bash, we are reading them in a PHP INI/FPM config file so the variable names are perfectly valid.&lt;/p&gt; &lt;/blockquote&gt; &lt;h3 id=&quot;child-images&quot;&gt;Child Images&lt;/h3&gt; &lt;p&gt;Now that I have created an image that defines all the INI settings I want to expose to env vars, I can create child images that can reference the env vars in their php.ini files!&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/master/files/php.ini&quot;&gt;You can see the INI file that all my images inject here.&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Any image that extends the previous image can use the defined env vars.&lt;/p&gt; &lt;p&gt;The following slice of php.ini:&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;allow_url_fopen&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${PHP.allow_url_fopen}&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;allow_url_include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${PHP.allow_url_include}&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;always_populate_raw_post_data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${PHP.always_populate_raw_post_data}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;is read as the following by the PHP engine:&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;allow_url_fopen&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;allow_url_include&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;always_populate_raw_post_data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Blank values are perfectly acceptable for these settings.&lt;/p&gt; &lt;p&gt;Likewise, in &lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/master/files/fpm.conf&quot;&gt;the PHP-FPM config file&lt;/a&gt; we have:&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;pm&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${FPM.pm}&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;pm.max_children&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${FPM.pm.max_children}&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;pm.start_servers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${FPM.pm.start_servers}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;which is read as&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;py&quot;&gt;pm&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dynamic&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;pm.max_children&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;pm.start_servers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;2&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;I deliberately chose not to add all possible settings. Like I mentioned earlier, FPM’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slowlog&lt;/code&gt; setting &lt;em&gt;must&lt;/em&gt; have a value. If it is blank FPM immediately dies with a segfault. Settings like this where developers may not always want to have but cannot leave blank are not friendly to the env var method.&lt;/p&gt; &lt;h2 id=&quot;removing-cmd-from-dockerfile&quot;&gt;Removing CMD from Dockerfile&lt;/h2&gt; &lt;p&gt;One thing I ran into was that the service defined via Dockerfile’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD&lt;/code&gt; will not receive updated env var values!&lt;/p&gt; &lt;p&gt;For example, in my &lt;a href=&quot;https://github.com/jtreminio/php-docker/commit/05c6a32e023e4095081620a6493c6da4a2823ce5&quot;&gt;PHP images I would have PHP-FPM service handled via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD&lt;/code&gt;&lt;/a&gt; and this service would pick up env var values as set at that point. If the Dockerfile had &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENV FPM.pm=dynamic&lt;/code&gt; that is what the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD&lt;/code&gt; service would use, even if you later changed the env var value.&lt;/p&gt; &lt;p&gt;The list of possible solutions included having the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD&lt;/code&gt; reload the current env vars, or starting a new session, or any number of hacky things just to get around this limitation.&lt;/p&gt; &lt;p&gt;What I ended up doing was simply removing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CMD&lt;/code&gt; from my Dockerfiles. Now the PHP images are truly open between being used for CLI or FPM! You simply call the PHP-FPM service when spinning up the container.&lt;/p&gt; &lt;h2 id=&quot;how-to-use-the-images&quot;&gt;How to Use the Images&lt;/h2&gt; &lt;p&gt;It is simple enough to use these images.&lt;/p&gt; &lt;p&gt;For the default INI values:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;error_reporting 195:error_reporting &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 0 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;To override the defaults:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; PHP.error_reporting&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;error_reporting 195:error_reporting &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You can just as easily override multiple settings:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; PHP.error_reporting&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; PHP.display_errors&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;On &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;PHP.date.timezone=America/Chicago&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 php &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; egrep &lt;span class=&quot;s1&quot;&gt;&apos;error_reporting|display_errors|date.timezone&apos;&lt;/span&gt; display_errors &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; STDOUT &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; STDOUT error_reporting &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; date.timezone &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; America/Chicago &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; America/Chicago &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;To run PHP-FPM you just call the service:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; PHP.error_reporting&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; PHP.display_errors&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;On &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;PHP.date.timezone=America/Chicago&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 /usr/bin/php-fpm &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Nov-2018 23:32:39] NOTICE: fpm is running, pid 9 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Nov-2018 23:32:39] NOTICE: ready to handle connections &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Nov-2018 23:32:39] NOTICE: systemd monitor interval &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;to 10000ms &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You can test if PHP-FPM is reading env vars as well by doing:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; PHP.error_reporting&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; PHP.display_errors&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;On &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;PHP.date.timezone=America/Chicago&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; FPM.pm&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;foobar &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 /usr/bin/php-fpm &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Nov-2018 23:33:08] ERROR: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;/etc/php/7.2/fpm/php-fpm.conf:14] unable to parse value &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;entry &lt;span class=&quot;s1&quot;&gt;&apos;pm&apos;&lt;/span&gt;: invalid process manager &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;static, dynamic or ondemand&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Nov-2018 23:33:08] ERROR: failed to load configuration file &lt;span class=&quot;s1&quot;&gt;&apos;/etc/php/7.2/fpm/php-fpm.conf&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;15-Nov-2018 23:33:08] ERROR: FPM initialization failed &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping It Up&lt;/h2&gt; &lt;p&gt;Not all services are amenable to configuration by environment variables, which is a shame. Tools like Nginx require special modules (Lua) for this functionality.&lt;/p&gt; &lt;p&gt;The PHP core team was forward-thinking enough to have added support for env vars long ago, and we get to enjoy its benefits.&lt;/p&gt; &lt;p&gt;Some future nice-to-haves would include default values, but for now this works pretty well.&lt;/p&gt; &lt;p&gt;Having to manage less files to run your Docker containers is a win in my book.&lt;/p&gt; &lt;p&gt;If you see some INI settings that you think should be included, &lt;a href=&quot;https://github.com/jtreminio/php-docker&quot;&gt;please submit a PR to the repo&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; </description> <pubDate>Thu, 15 Nov 2018 00:00:00 -0600</pubDate> <link>https://jtreminio.com/blog/docker-php-php-fpm-configuration-via-environment-variables/</link> <guid isPermaLink="true">https://jtreminio.com/blog/docker-php-php-fpm-configuration-via-environment-variables/</guid> <category>docker</category> <category>php</category> <category>blog</category> </item> <item> <title>Setting Up a Static Site with Hugo and Push to Deploy</title> <description>&lt;p&gt;For several years this blog was generated using the PHP static site generator &lt;a href=&quot;https://sculpin.io/&quot;&gt;Sculpin&lt;/a&gt;. I switched to &lt;a href=&quot;https://getgrav.org/&quot;&gt;Grav&lt;/a&gt; before deciding it was not for me. My blog does not contain dynamic data that requires PHP processing, and static HTML with JS is fine.&lt;/p&gt; &lt;p&gt;One of the issues I had with Grav was its requirement of both a PHP-FPM and Nginx/Apache service to properly serve content.&lt;/p&gt; &lt;p&gt;After researching available options, I decided to switch to the amazing &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Hugo has several benefits over other generators:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;completely static output is generated from Markdown/HTML files,&lt;/li&gt; &lt;li&gt;a single Go binary with no outside dependencies, or a container can be used to generate static files,&lt;/li&gt; &lt;li&gt;tons of themes,&lt;/li&gt; &lt;li&gt;only requires a webserver for production deployment (Nginx/Apache)&lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&quot;our-goals&quot;&gt;Our goals&lt;/h2&gt; &lt;p&gt;Today I will walk you through the complete process of setting up a static website that you can deploy new versions with a simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;Pushing to your repo will trigger an automated build process that will generate minified HTML/CSS/JS files, package them in an Nginx image, add a new image to &lt;a href=&quot;https://hub.docker.com/&quot;&gt;Docker Hub&lt;/a&gt;, deploy a new container on your host and automatically generate and maintain free SSL certificates using &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;The process outlined here is what I have created and use for this blog, &lt;a href=&quot;https://jtreminio.com&quot;&gt;jtreminio.com&lt;/a&gt;. Each new build and deployment currently takes around a few minutes and is completely automated.&lt;/p&gt; &lt;p&gt;The only prerequisite is you have Docker installed on your local machine. All other dependencies will be in Docker containers.&lt;/p&gt; &lt;h2 id=&quot;technology-and-services-used&quot;&gt;Technology and services used&lt;/h2&gt; &lt;p&gt;We will configure and run several technologies, including:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt; for static site generator,&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://www.ansible.com/&quot;&gt;Ansible&lt;/a&gt; for configuring the server,&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; for containers,&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://traefik.io/&quot;&gt;Traefik&lt;/a&gt; for routing traffic to correct container, and automatic SSL certificates,&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://github.com/v2tec/watchtower&quot;&gt;Watchtower&lt;/a&gt; for keeping latest Docker image running on your site.&lt;/li&gt; &lt;li&gt;&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; for free, automated SSL certificate.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Everything that is used is completely free and open sourced, other than the host. If you are in need of a host I can recommend &lt;a href=&quot;https://www.digitalocean.com/?refcode=475274cc0939&quot;&gt;Digital Ocean&lt;/a&gt; &lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. The basic $5/month plan will be more than enough. If you have your own host you would prefer to use, by all means use it!&lt;/p&gt; &lt;h2 id=&quot;configure-server&quot;&gt;Configure server&lt;/h2&gt; &lt;p&gt;First we need to install Docker, Traefik and Watchtower on the server.&lt;/p&gt; &lt;p&gt;I have created a &lt;a href=&quot;https://github.com/jtreminio/docker-bootstrap&quot;&gt;simple Ansible-based configuration&lt;/a&gt; that&lt;/p&gt; &lt;ul&gt; &lt;li&gt;installs Docker,&lt;/li&gt; &lt;li&gt;opens ports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;443&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8080&lt;/code&gt; on firewall (optional),&lt;/li&gt; &lt;li&gt;adds Traefik and configures Let’s Encrypt support,&lt;/li&gt; &lt;li&gt;creates a Watchtower container.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;The only things you must configure are all handled by creating a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file and filling out the following:&lt;/p&gt; &lt;table class=&quot;table table-striped&quot;&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;name&lt;/th&gt; &lt;th&gt;description&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;SERVER_IP&lt;/td&gt; &lt;td&gt;IP address of server&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SSH_USER&lt;/td&gt; &lt;td&gt;SSH username, “root” on Ubuntu&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;SSH_PRIVATE_KEY&lt;/td&gt; &lt;td&gt;Path to SSH private key on your machine&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;LE_EMAIL&lt;/td&gt; &lt;td&gt;Email to use for Let’s Encrypt&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;h3 id=&quot;run-ansible&quot;&gt;Run Ansible&lt;/h3&gt; &lt;p&gt;You can start Ansible by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./init&lt;/code&gt; in the root of the repo directory.&lt;/p&gt; &lt;p&gt;It will create an Ansible container on your local machine that will connect to and configure your defined remote servers.&lt;/p&gt; &lt;p&gt;The local Ansible container is removed once it finishes running.&lt;/p&gt; &lt;p&gt;If all goes well you should see something similar to&lt;/p&gt; &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;PLAY RECAP ********************************************** remote : ok=8 changed=8 unreachable=0 failed=0 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The important part is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;failed=0&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;If you go to your blog’s URL you will see an invalid certificate warning.&lt;/p&gt; &lt;p&gt;This is fine! We have not actually created the blog container and Traefik has not generated an SSL certificate for it yet.&lt;/p&gt; &lt;h2 id=&quot;setting-up-hugo&quot;&gt;Setting up Hugo&lt;/h2&gt; &lt;p&gt;Next we will get Hugo running on our local machine.&lt;/p&gt; &lt;p&gt;I have created a &lt;a href=&quot;https://github.com/jtreminio/hugoBasicExample&quot;&gt;Hugo bootstrap repo&lt;/a&gt; that comes with some tools already added.&lt;/p&gt; &lt;p&gt;All you need to do is clone two repos:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;git clone https://github.com/jtreminio/hugoBasicExample.git &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;hugoBasicExample git clone https://github.com/nanxiaobei/hugo-paper.git &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; themes/hugo-paper &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;And you can start the Hugo server:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;./bin/hugo-server &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Now open http://localhost:1313/ and you will see a fully working Hugo blog.&lt;/p&gt; &lt;p&gt;Feel free to explore Hugo in more detail by visiting &lt;a href=&quot;https://gohugo.io/&quot;&gt;gohugo.io&lt;/a&gt;.&lt;/p&gt; &lt;h3 id=&quot;manual-deployment-process-steps&quot;&gt;Manual deployment process steps&lt;/h3&gt; &lt;p&gt;With a single command Hugo takes all your HTML/Markdown content and generates static files in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/public&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;You can see this process by running&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;./bin/hugo-publish &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You will see your Markdown posts in HTML files, nested within directories that match your blog structure. Hugo also copies over all CSS/JS/etc files that are in your root &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/static&lt;/code&gt; or the theme’s.&lt;/p&gt; &lt;p&gt;You &lt;em&gt;could&lt;/em&gt; take all this static content and deploy to production as-is, but we can run some minify tools to get the file sizes down.&lt;/p&gt; &lt;p&gt;Hugo does no post-processing and everything must be done by third-party tools. I have added a minify script that you can run with:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;./bin/minify &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;It takes all HTML, CSS and JS files and minifies them down to a much smaller size.&lt;/p&gt; &lt;p&gt;Finally, you can run an Nginx container to make sure your site looks properly.&lt;/p&gt; &lt;p&gt;This local Nginx container will be exactly the same as what you deploy to production:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;./bin/nginx &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;All these steps can be run manually, but that is a waste of time. Better to automate the process!&lt;/p&gt; &lt;h2 id=&quot;docker-multi-stage-builds&quot;&gt;Docker multi-stage builds&lt;/h2&gt; &lt;p&gt;Our end goal is to automate the 3 steps above and end up with a single, tiny image we can deploy to our production server (automatically).&lt;/p&gt; &lt;p&gt;Docker recently came out with &lt;a href=&quot;https://docs.docker.com/develop/develop-images/multistage-build/&quot;&gt;multi-stage builds&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;It means you can create a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; with as many sequential stages as you need to generate a single, final image.&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/jtreminio/hugoBasicExample/blob/master/Dockerfile&quot;&gt;I have created a Dockerfile&lt;/a&gt; which takes the 3 steps above and runs through them. If you create the image on your computer you will end up with a single, tiny container at the end:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker image build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; hugo-test &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Sending build context to Docker daemon 1.383MB Step 1/17 : FROM alpine/git &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 1e76d5809b62 Step 2/17 : COPY &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; /data &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; a473877e4ad9 Step 3/17 : WORKDIR /data &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;6e1b6e2796a4 Removing intermediate container 6e1b6e2796a4 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 1fcaafec077f Step 4/17 : RUN &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; themes/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;020d0c4f303f Removing intermediate container 020d0c4f303f &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 00a81909f7a0 Step 5/17 : RUN git clone https://github.com/nanxiaobei/hugo-paper.git themes/hugo-paper &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;4d7c71cd51ac Cloning into &lt;span class=&quot;s1&quot;&gt;&apos;themes/hugo-paper&apos;&lt;/span&gt;... Removing intermediate container 4d7c71cd51ac &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 5e67ef78f4b8 Step 6/17 : FROM skyscrapers/hugo:0.46 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 434ff241d9e8 Step 7/17 : COPY &lt;span class=&quot;nt&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 /data /data &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 3d27347872c5 Step 8/17 : WORKDIR /data &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;f0875071a444 Removing intermediate container f0875071a444 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ca8120476886 Step 9/17 : RUN hugo &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;e2b6817fe000 | EN +------------------+----+ Pages | 12 Paginator pages | 0 Non-page files | 0 Static files | 2 Processed images | 0 Aliases | 0 Sitemaps | 1 Cleaned | 0 Total &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;15 ms Removing intermediate container e2b6817fe000 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; cc2be3328f07 Step 10/17 : FROM mysocialobservations/docker-tdewolff-minify &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 43c3688d88ad Step 11/17 : COPY &lt;span class=&quot;nt&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1 /data/public /data/public &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 144634f56841 Step 12/17 : WORKDIR /data &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;404cb0f24509 Removing intermediate container 404cb0f24509 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; d0a02742aa3c Step 13/17 : RUN minify &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--verbose&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--match&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.js&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;js &lt;span class=&quot;nt&quot;&gt;--output&lt;/span&gt; public/ public/ &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;38f21d784856 INFO: use mimetype text/javascript INFO: expanding directory public/ recursively INFO: minify input file public/js/custom.js INFO: minify to output directory public/ INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 68.167µs, 32 B, 49.2%, 954 kB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/js/custom.js INFO: 3.055423ms total Removing intermediate container 38f21d784856 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; d0d80a7d1ab1 Step 14/17 : RUN minify &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--verbose&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--match&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.css&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;css &lt;span class=&quot;nt&quot;&gt;--output&lt;/span&gt; public/ public/ &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;81e9840eb053 INFO: use mimetype text/css INFO: expanding directory public/ recursively INFO: minify input file public/css/style.css INFO: minify to output directory public/ INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;389.209µs, 6.0 kB, 100.0%, 15 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/css/style.css INFO: 3.797968ms total Removing intermediate container 81e9840eb053 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 80108c675341 Step 15/17 : RUN minify &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--verbose&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--match&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.html&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;html &lt;span class=&quot;nt&quot;&gt;--output&lt;/span&gt; public/ public/ &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;d0c1c70b3e80 INFO: use mimetype text/html INFO: expanding directory public/ recursively INFO: minify 30 input files INFO: minify to output directory public/ INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;283.292µs, 2.2 kB, 99.7%, 7.6 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/404.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;192.584µs, 3.4 kB, 99.8%, 18 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/about/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;286.917µs, 3.7 kB, 99.8%, 13 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/categories/development/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 68µs, 275 B, 100.0%, 4.0 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/categories/development/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;219.375µs, 3.7 kB, 99.8%, 17 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/categories/golang/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 56.709µs, 260 B, 100.0%, 4.6 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/categories/golang/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;253.918µs, 2.7 kB, 99.8%, 11 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/categories/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 56.625µs, 239 B, 100.0%, 4.2 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/categories/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;272.709µs, 5.8 kB, 99.9%, 21 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 68.126µs, 206 B, 100.0%, 3.0 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;1.140128ms, 56 kB, 100.0%, 49 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/post/creating-a-new-theme/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;487.084µs, 15 kB, 100.0%, 30 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/post/goisforlovers/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;317.792µs, 5.8 kB, 99.9%, 18 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/post/hugoisforlovers/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;252.542µs, 5.2 kB, 99.9%, 21 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/post/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;349.251µs, 11 kB, 99.9%, 31 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/post/migrate-from-jekyll/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 77µs, 221 B, 100.0%, 2.9 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/post/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;317.834µs, 3.8 kB, 99.8%, 12 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/development/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 80.792µs, 257 B, 100.0%, 3.2 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/development/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;351.584µs, 3.8 kB, 99.8%, 11 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/go/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 68.083µs, 230 B, 100.0%, 3.4 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/go/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;280.542µs, 3.8 kB, 99.8%, 14 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/golang/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 69.042µs, 242 B, 100.0%, 3.5 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/golang/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;255.334µs, 3.0 kB, 99.8%, 12 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/hugo/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 68.417µs, 236 B, 100.0%, 3.4 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/hugo/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;221.125µs, 3.7 kB, 99.8%, 17 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 81.125µs, 221 B, 100.0%, 2.7 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;198.083µs, 2.9 kB, 99.8%, 15 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/templates/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;118.167µs, 251 B, 100.0%, 2.1 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/templates/page/1/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 242µs, 2.9 kB, 99.8%, 12 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/themes/index.html INFO: &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt; 67.75µs, 242 B, 100.0%, 3.6 MB/s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; - public/tags/themes/page/1/index.html INFO: 112.866806ms total Removing intermediate container d0c1c70b3e80 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; f31a796feec7 Step 16/17 : FROM nginx:alpine &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 36f3464a2197 Step 17/17 : COPY &lt;span class=&quot;nt&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2 /data/public /usr/share/nginx/html &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 5c0ffab7f6be Successfully built 5c0ffab7f6be Successfully tagged hugo-test:latest &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Inside this single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; are 4 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FROM&lt;/code&gt; sections. What Docker actually ends up doing is creating 3 intermediary images, and one final image. The final image contains nothing but what you explicitly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COPY&lt;/code&gt; into it, and the end result is a tiny image:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker image &lt;span class=&quot;nb&quot;&gt;ls &lt;/span&gt;REPOSITORY &amp;lt;... snip ...&amp;gt; SIZE &amp;lt;none&amp;gt; &amp;lt;... snip ...&amp;gt; 262MB hugo-test &amp;lt;... snip ...&amp;gt; 18.8MB &amp;lt;none&amp;gt; &amp;lt;... snip ...&amp;gt; 106MB &amp;lt;none&amp;gt; &amp;lt;... snip ...&amp;gt; 25.1MB &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This tiny image is what we end up deploying to production. It contains Nginx and all the static, minified files.&lt;/p&gt; &lt;p&gt;You can test it yourself by running:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 8080:80 hugo-test &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;and going to http://localhost:8080&lt;/p&gt; &lt;h2 id=&quot;docker-hub-automated-builds&quot;&gt;Docker Hub Automated Builds&lt;/h2&gt; &lt;p&gt;After you have played around a bit with Hugo, commit any changes you have made and push to a public repo on Github.&lt;/p&gt; &lt;p&gt;In our next steps we will get the Docker Hub to do the exact same process as above whenever we push a new change to Github.&lt;/p&gt; &lt;p&gt;If you do not yet have an account, create one at &lt;a href=&quot;https://hub.docker.com/&quot;&gt;hub.docker.com&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;We are now going to grant the Docker Hub to access our Github repos and add hooks.&lt;/p&gt; &lt;p&gt;This simply means whenever a commit is pushed to the repo, Github will notify the Docker Hub and it will automatically create a new image for us.&lt;/p&gt; &lt;p&gt;Go to &lt;a href=&quot;https://hub.docker.com/account/authorized-services/&quot;&gt;Linked Accounts &amp;amp; Services&lt;/a&gt; and follow the directions.&lt;/p&gt; &lt;p&gt;Next, go to the &lt;a href=&quot;https://hub.docker.com/add/automated-build/github/&quot;&gt;Automated Builds&lt;/a&gt; page and click &lt;a href=&quot;https://hub.docker.com/add/automated-build/github/github/orgs/&quot;&gt;Create Auto-build Github&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;From there you can find the repo you created earlier.&lt;/p&gt; &lt;blockquote class=&quot;warning&quot;&gt; &lt;p&gt;There are currently two bugs with the Docker Hub GUI when creating an automated build.&lt;/p&gt; &lt;ol&gt; &lt;li&gt;The repository you create for the automated build must not exist on Docker Hub. For example, my Hub username is &lt;em&gt;jtreminio&lt;/em&gt; and my repo’s name is &lt;em&gt;jtreminio.com&lt;/em&gt; (&lt;a href=&quot;https://hub.docker.com/r/jtreminio/jtreminio.com/&quot;&gt;found here&lt;/a&gt;). Using &lt;a href=&quot;https://hub.docker.com/add/automated-build/github/github/orgs/&quot;&gt;the GUI found here&lt;/a&gt; the Hub will auto-populate the fields for you, even if you already have a repo by that name! Either change the name on this page or delete your existing repo. This is on the &lt;em&gt;Docker Hub&lt;/em&gt;, &lt;strong&gt;not&lt;/strong&gt; on &lt;em&gt;Github&lt;/em&gt;!&lt;/li&gt; &lt;li&gt;The URL you end up in, &lt;em&gt;after&lt;/em&gt; &lt;a href=&quot;https://hub.docker.com/add/automated-build/github/github/orgs/&quot;&gt;the GUI found here&lt;/a&gt;, may be &lt;em&gt;incorrect&lt;/em&gt;! For me it generated a URL that ended with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/github/form/jtreminio/jtreminio.com/?namespace=github&lt;/code&gt;. This &lt;em&gt;silently&lt;/em&gt; fails when you submit the form. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?namespace=&lt;/code&gt; part should actually contain your Docker Hub username! I had to change my URL to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/github/form/jtreminio/jtreminio.com/?namespace=jtreminio&lt;/code&gt;&lt;/li&gt; &lt;/ol&gt; &lt;/blockquote&gt; &lt;p&gt;After you follow the instructions you will find the Docker Hub repo page now includes several more options than before, including &lt;em&gt;Dockerfile&lt;/em&gt;, &lt;em&gt;Build Details&lt;/em&gt;, and &lt;em&gt;Build Settings&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;If you go to &lt;em&gt;Build Settings&lt;/em&gt; you can manually start your first build by clicking &lt;em&gt;Trigger&lt;/em&gt; on the right side of the page. This may take a few minutes.&lt;/p&gt; &lt;h2 id=&quot;starting-your-blog&quot;&gt;Starting your blog&lt;/h2&gt; &lt;p&gt;Once the first build is finished on the Docker Hub we can create the initial container for our blog on our server.&lt;/p&gt; &lt;p&gt;SSH into your server and run the following:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jtreminio_com &lt;span class=&quot;nv&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jtreminio.com &lt;span class=&quot;nv&quot;&gt;IMAGE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;jtreminio/jtreminio.com&quot;&lt;/span&gt; docker container run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.backend&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.docker.network&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.frontend.rule&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Host:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HOST&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.port&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;80 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; com.centurylinklabs.watchtower.enable&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--restart&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;IMAGE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Make sure to change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NAME&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HOST&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IMAGE&lt;/code&gt; to your own information!&lt;/p&gt; &lt;p&gt;A few things will now happen:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;The container with your website inside will start,&lt;/li&gt; &lt;li&gt;Traefik detects this new container and automatically generates a new, free SSL certificate from Let’s Encrypt. It will continue monitoring this certificate and renew it long before it expires, all without you needing to worry about it.&lt;/li&gt; &lt;li&gt;Watchtower takes note of this new container, but does nothing right now.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;If you go to your website URL you will see your blog up and running with a brand new SSL certificate!&lt;/p&gt; &lt;h2 id=&quot;watchtower&quot;&gt;Watchtower&lt;/h2&gt; &lt;p&gt;So what exactly does Watchtower do? If you run&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker container logs watchtower &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;you may not see anything very interesting at first. The magic happens when you make changes to your website, commit and push to Github, and after the Docker Hub automatically creates a new image of your website.&lt;/p&gt; &lt;p&gt;Watchtower polls the Docker Hub every few minutes to detect if any of the containers you are currently running have new image versions. Once Docker Hub finishes creating the new image with the latest changes of your website, Watchtower will automatically download the image, gracefully shut down your blog container and immediately restart it with the new image, and your new changes.&lt;/p&gt; &lt;p&gt;Here is what the logs show when this happens:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;root@docker:/opt# docker container logs &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; watchtower &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2018-08-09T00:28:50Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;First run: 2018-08-09 00:33:50 +0000 UTC&quot;&lt;/span&gt; // ... &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2018-08-09T00:33:53Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Found new jtreminio/jtreminio.com:latest image (sha256:5a8c9299091b6892753128792a6d6c90f26dd27ed10c5286b3fc8f0b8799c503)&quot;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2018-08-09T00:33:57Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Stopping /jtreminio_com (ebae9539acfcedf2279115f2c19ebddaf3c34271aa5d048142c6b90d091bf987) with SIGTERM&quot;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2018-08-09T00:33:58Z&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;info &lt;span class=&quot;nv&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Creating /jtreminio_com&quot;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Watchtower can monitor any number of containers and is the final piece in our automated puzzle.&lt;/p&gt; &lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt; &lt;p&gt;Today you learned how to utilize free, open source tools to automate your blog deployment process.&lt;/p&gt; &lt;p&gt;While Docker Hub automated builds may not be suitable for more complex requirements, it can easily meet what we created today.&lt;/p&gt; &lt;p&gt;No more FTP, nor more pulling from repo directly from your server. Automating this boring and error-prone process helps lift a small weight off of your shoulders and lets you focus on what you enjoy doing best: writing about things you love.&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; &lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt; &lt;ol&gt; &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt; &lt;p&gt;Affiliate link, help support this free blog! &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;/div&gt; </description> <pubDate>Wed, 08 Aug 2018 00:00:00 -0500</pubDate> <link>https://jtreminio.com/blog/setting-up-a-static-site-with-hugo-and-push-to-deploy/</link> <guid isPermaLink="true">https://jtreminio.com/blog/setting-up-a-static-site-with-hugo-and-push-to-deploy/</guid> <category>docker</category> <category>lets-encrypt</category> <category>ansible</category> <category>hugo</category> <category>blog</category> </item> <item> <title>Running Docker Containers as Current Host User</title> <description>&lt;p&gt;&lt;em&gt;ed: If you want to jump right to the solution, jump ahead to &lt;strong&gt;&lt;a href=&quot;#ok-so-what-actually-works&quot;&gt;Ok so what actually works?&lt;/a&gt;&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;Docker is an excellent tool for local web development. It allows creating non-trivial environments without polluting the local system with tools.&lt;/p&gt; &lt;p&gt;There are still some things that make working with it just a tad bit harder than necessary.&lt;/p&gt; &lt;p&gt;Today’s topic involves running Docker containers using the local host system’s current logged-in user.&lt;/p&gt; &lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt; &lt;p&gt;You are working on a project that requires Node NPM, PHP Composer or a similar tool that downloads or compiles outside dependencies or assets for you.&lt;/p&gt; &lt;p&gt;You do not want to install the language (PHP, Node) locally to run this tool, so you choose to run a Docker container. Here is how it would look like to spin up a container with PHP Composer support:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 composer require psr/log &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This starts a temporary container using my &lt;a href=&quot;https://hub.docker.com/r/jtreminio/php/&quot;&gt;PHP 7.2 image&lt;/a&gt; that has Composer pre-installed and runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer require psr/log&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;It generates the following:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-la&lt;/span&gt; total 20K drwxrwxr-x. 3 jtreminio jtreminio 4.0K Aug 4 19:34 ./ drwxr-xr-x. 7 jtreminio jtreminio 4.0K Aug 4 19:31 ../ drwxr-xr-x. 4 root root 4.0K Aug 4 19:34 vendor/ &lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; 1 root root 53 Aug 4 19:34 composer.json &lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; 1 root root 2.1K Aug 4 19:34 composer.lock &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Although it &lt;em&gt;works&lt;/em&gt; as intended, the problem is that the directories and files generated by Composer are root-owned.&lt;/p&gt; &lt;p&gt;Since I do not use root, attempting to do anything other than read the files is blocked:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; composer.json bash: composer.json: Permission denied &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You have to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; to edit or remove everything. This quickly gets old when your project uses Composer, Webpack, Yarn, etc because your system becomes littered with root-owned files and directories.&lt;/p&gt; &lt;h2 id=&quot;the-reason&quot;&gt;The Reason&lt;/h2&gt; &lt;p&gt;Docker on Linux runs as a daemon. The official installation instructions recommend installing as root and selectively adding users to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt; group so they can run all Docker commands.&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ps &lt;span class=&quot;nt&quot;&gt;-fe&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;dockerd 255:root 2356 1 0 Aug03 ? 00:04:06 /usr/bin/dockerd &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;When you create a new container it does not get created as your current user, but as root, which the daemon is running under.&lt;/p&gt; &lt;p&gt;We can verify that the container runs as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; with user/group ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0:0&lt;/code&gt;:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;nb&quot;&gt;whoami &lt;/span&gt;root &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;echo &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;(id -u &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;{USER}):&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;(id -g &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;{USER})&quot;&lt;/span&gt; 0:0 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;so-run-as-your-local-user-right&quot;&gt;So run as your local user, right?&lt;/h2&gt; &lt;p&gt;When you run Docker containers you can specify a user ID, plus a group ID. It is easy enough to do:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 composer require psr/log &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;This generates the following:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-la&lt;/span&gt; total 20K drwxrwxr-x. 3 jtreminio jtreminio 4.0K Aug 4 20:09 ./ drwxr-xr-x. 7 jtreminio jtreminio 4.0K Aug 4 19:31 ../ drwxr-xr-x. 4 jtreminio jtreminio 4.0K Aug 4 20:09 vendor/ &lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; 1 jtreminio jtreminio 53 Aug 4 20:09 composer.json &lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; 1 jtreminio jtreminio 2.1K Aug 4 20:09 composer.lock &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;blockquote class=&quot;info&quot;&gt; &lt;p&gt;In my system, my user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jtreminio&lt;/code&gt; has user ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000&lt;/code&gt; and group ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000&lt;/code&gt;, so the new line&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-u $(id -u ${USER}):$(id -g ${USER})&lt;/code&gt;&lt;/p&gt; &lt;p&gt;gets interpreted as&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-u 1000:1000&lt;/code&gt;&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;This does exactly what we want, but of course there is a catch: the container user is no longer root, or whatever the author decided to use. On Composer and NPM this simply means any internal cache directories cannot be written to since they are root-owned, but that really is not much of a problem because we are tearing the containers down as soon as they finish running what we told them to.&lt;/p&gt; &lt;p&gt;The Composer container above is deleted as soon as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;composer require psr/log&lt;/code&gt; finishes executing.&lt;/p&gt; &lt;p&gt;What if you want to run a web app with PHP-FPM? It must be able to create its PID file at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/run/php-fpm.pid&lt;/code&gt;, if you are using file-based sessions it must write to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/php/sessions&lt;/code&gt;. Any number of things that require root or a predefined user will no longer work because the container is &lt;em&gt;running using your user/group ID&lt;/em&gt;:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;nb&quot;&gt;whoami whoami&lt;/span&gt;: cannot find name &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;user ID 1000 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;echo &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;(id -u &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;{USER}):&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;(id -g &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;{USER})&quot;&lt;/span&gt; 1000:1000 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you try to do anything that requires elevated permissions or a specific user, you will be denied:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-la&lt;/span&gt; /var/lib/php/sessions total 8 drwxr-xr-x. 1 www-data www-data 4096 Jul 9 12:35 &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; drwxr-xr-x. 1 root root 4096 Jul 26 09:08 .. &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;nb&quot;&gt;touch&lt;/span&gt; /var/lib/php/sessions/foo &lt;span class=&quot;nb&quot;&gt;touch&lt;/span&gt;: cannot &lt;span class=&quot;nb&quot;&gt;touch&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;/var/lib/php/sessions/foo&apos;&lt;/span&gt;: Permission denied &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The above means running the PHP-FPM daemon as your local user will quickly encounter permissions issues. In this case it is because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/php/sessions&lt;/code&gt; is owned by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data:www-data&lt;/code&gt; which most likely does not share your local user’s IDs:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;echo &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;(id -u www-data):&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;(id -g www-data)&quot;&lt;/span&gt; 33:33 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;ok-run-it-as-an-non-root-internal-user&quot;&gt;OK, run it as an non-root internal user?&lt;/h2&gt; &lt;p&gt;So far we have found that&lt;/p&gt; &lt;ul&gt; &lt;li&gt;root works great inside the container but is annoying to work with on the host system, and&lt;/li&gt; &lt;li&gt;your local user works great on your host system, but will quickly run into permission problems inside the container.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;What if we try to run the containers as a non-root user that has required permissions to write inside the container directories?&lt;/p&gt; &lt;p&gt;Try with the container’s internal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; user:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; www-data &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;touch /var/lib/php/sessions/foo &amp;amp;&amp;amp; echo &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;?&quot;&lt;/span&gt; 0 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;As expected this worked fine, but if you try running Composer:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; www-data &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 composer require psr/log &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;ErrorException] file_put_contents&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;./composer.json&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;: failed to open stream: Permission denied &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The problem is that the internal container user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; with user/group ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;33:33&lt;/code&gt;, does not have write permissions to my host’s current directory:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-la&lt;/span&gt; total 8.0K drwxrwxr-x. 2 jtreminio jtreminio 4.0K Aug 4 20:34 ./ drwxr-xr-x. 7 jtreminio jtreminio 4.0K Aug 4 19:31 ../ &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We might as well add another grievance to our list:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;non-root internal user with required permissions works great inside the container but completely falters on host system with volumes.&lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&quot;why-is-this-happening&quot;&gt;Why is this happening?&lt;/h2&gt; &lt;p&gt;Host and containers do not share users and groups.&lt;/p&gt; &lt;p&gt;On my local system, there is no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; user:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;groups &lt;/span&gt;www-data &lt;span class=&quot;nb&quot;&gt;groups&lt;/span&gt;: ‘www-data’: no such user &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Similarly, if I try using my direct username to run the container I see errors:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; jtreminio &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;nb&quot;&gt;whoami &lt;/span&gt;docker: Error response from daemon: linux spec user: unable to find user jtreminio: no matching entries &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;passwd file. &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;As far as the container is aware, it is its own separate system with its own list of users at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/passwd&lt;/code&gt; and list of groups at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/group&lt;/code&gt;. It has no idea about any users or groups that exist on the host system. Likewise, the host does not know about users in the container. By convention root is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0:0&lt;/code&gt; which will match any Linux system, but anything else is up to each distro and each image author.&lt;/p&gt; &lt;h2 id=&quot;try-sharing-etcpasswd&quot;&gt;Try sharing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/passwd&lt;/code&gt;!&lt;/h2&gt; &lt;p&gt;Now we know that the&lt;/p&gt; &lt;ul&gt; &lt;li&gt;host system does not know about container’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/passwd&lt;/code&gt;, and&lt;/li&gt; &lt;li&gt;container does not know about the host system’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/passwd&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;What happens if we bind the host’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/passwd&lt;/code&gt; to the container’s?&lt;/p&gt; &lt;p&gt;Not much as you would think, actually. My host system still does not have a user matching &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;33:33&lt;/code&gt;, so even if I bind the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/passwd&lt;/code&gt; into the container, attempting to write to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt;-owned directories still will not work.&lt;/p&gt; &lt;p&gt;This is because Linux permissions are not name-based, but ID-based.&lt;/p&gt; &lt;p&gt;For example, on my local system:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-lan&lt;/span&gt; total 8.0K drwxrwxr-x. 2 1000 1000 4.0K Aug 4 20:34 ./ drwxr-xr-x. 7 1000 1000 4.0K Aug 4 19:31 ../ &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;and in the container:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; jtreminio/php:7.2 &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-lan&lt;/span&gt; /var/lib/php/sessions total 8 drwxr-xr-x. 1 33 33 4096 Jul 9 12:35 &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; drwxr-xr-x. 1 0 0 4096 Jul 26 09:08 .. &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;In the container &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data:www-data&lt;/code&gt; is simply an alias to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;33:33&lt;/code&gt;.&lt;/p&gt; &lt;h2 id=&quot;user-namespaces&quot;&gt;User namespaces&lt;/h2&gt; &lt;p&gt;The container does not care what user and group name is used, it simply wants the IDs to match.&lt;/p&gt; &lt;p&gt;There is a concept in Docker Engine called &lt;em&gt;User Namespaces&lt;/em&gt;. &lt;a href=&quot;https://www.jujens.eu/posts/en/2017/Jul/02/docker-userns-remap/&quot;&gt;Here is a great introduction to them.&lt;/a&gt;&lt;/p&gt; &lt;p&gt;The concept boils down to mapping the internal container user/group IDs to reflect different values.&lt;/p&gt; &lt;p&gt;For example, you can tell Docker to use your current user/group ID as the “floor” for container IDs. In my example, my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jtreminio&lt;/code&gt; account with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000:1000&lt;/code&gt; would map directly to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0:0&lt;/code&gt; in a container.&lt;/p&gt; &lt;p&gt;In other words, we tell Docker to consider our current user on the host as root in containers! Local system user ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000&lt;/code&gt; maps directly to container user ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;User namespaces would allow us to run all containers as root internally which would completely eliminate any permission issues, and any generated files and directories on shared volumes would be owned by the host user/group so we would no longer need to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; to edit or delete them.&lt;/p&gt; &lt;p&gt;This sounds exactly what we need, right? There is a catch (because of course there is)… in fact, multiple catches!&lt;/p&gt; &lt;h4 id=&quot;you-are-running-the-container-as-root&quot;&gt;You are running the container as root&lt;/h4&gt; &lt;p&gt;Normally this would be a security concern. Countless articles and guides strongly recommend &lt;em&gt;not&lt;/em&gt; running as root in containers.&lt;/p&gt; &lt;p&gt;In this case, this is not the problem, because the container’s internal root maps to our current, non-root user on host.&lt;/p&gt; &lt;p&gt;The problem actually comes down to root being able to do anything and everything in the container, which opens the door to unforeseen permissions issues when you deploy to production, where you would not be using user namespaces.&lt;/p&gt; &lt;p&gt;In production, most containers will run as non-root, with permissions specifically tailored for that user/group ID. Attempting to do anything the user is not permitted will rightfully result in permissions denied errors. This is a safety feature you &lt;em&gt;want&lt;/em&gt;, and running as root completely ignores this.&lt;/p&gt; &lt;p&gt;You might run as root on your local system and everything works perfectly fine. You are happy, files are generated as expected, all actions are permitted and you are a happy developer. Once you deploy to production you may suddenly realize that you were developing on a system very much unlike production. Something that worked flawlessly on your development system can easily break on production due to insufficient permissions for whatever user is running the container service.&lt;/p&gt; &lt;h4 id=&quot;your-usergroup-id-is-the-floor-it-goes-up&quot;&gt;Your user/group ID is the &lt;em&gt;floor&lt;/em&gt;, it goes up!&lt;/h4&gt; &lt;p&gt;Your user/group ID maps directly to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0:0&lt;/code&gt; in the container, but there are more accounts than just the one root and none of them are mapped to your host user.&lt;/p&gt; &lt;p&gt;This means that while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0:0&lt;/code&gt; maps to my host system’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000:1000&lt;/code&gt;, the container’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; user with IDs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;33:33&lt;/code&gt; would map to my host system’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1033:1033&lt;/code&gt;, which does not exist.&lt;/p&gt; &lt;p&gt;Anything done as non-root in the container will run against the same issues we saw earlier: what might be considered sufficient permissions inside the container will probably not work the same on your host.&lt;/p&gt; &lt;p&gt;My host’s directories are still owned by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000:1000&lt;/code&gt; and a user with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1033:1033&lt;/code&gt; will be denied.&lt;/p&gt; &lt;h4 id=&quot;all-containers-on-your-system-are-affected&quot;&gt;All containers on your system are affected&lt;/h4&gt; &lt;p&gt;If the previous two reasons are not enough, the fact that Docker does not come with User Namespaces support enabled by default, to enable it requires editing a JSON config file and restarting the Docker daemon, and now all your existing and future containers will implement namespaces whether you want them to or not should be enough to make you question if it is worth the hassle.&lt;/p&gt; &lt;h2 id=&quot;ok-so-what-actually-works&quot;&gt;Ok so what actually works?&lt;/h2&gt; &lt;p&gt;The only sure-fire way of resolving all previous issues and getting on with Making the World a Better Place is completely replacing the internal user/group IDs with known, good values.&lt;/p&gt; &lt;p&gt;In the container, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; maps to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;33:33&lt;/code&gt;. Why not change it so it maps to my host system’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jtreminio&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000:1000&lt;/code&gt;?&lt;/p&gt; &lt;p&gt;In other words, make the container’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; user/group ID be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000:1000&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;You cannot change an existing account’s user/group IDs. Remember, the user and group names are simply aliases to the IDs. You can &lt;em&gt;rename&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; to anything you want but the IDs will not change.&lt;/p&gt; &lt;p&gt;The only thing you can do is &lt;em&gt;delete&lt;/em&gt; and &lt;em&gt;recreate&lt;/em&gt; the user/group completely.&lt;/p&gt; &lt;p&gt;For this we will need to create a Dockerfile. We need to recreate the user before any potential entrypoint scripts are run. Unknown issues can and will occur if you attempt to delete a user/group while it is in use by another process.&lt;/p&gt; &lt;p&gt;First, delete the user and group:&lt;/p&gt; &lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; jtreminio/php:7.2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;userdel &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;getent group www-data &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;groupdel www-data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We test if the group actually exists before trying to delete, to avoid possible errors when the group does not exist.&lt;/p&gt; &lt;p&gt;Next, we can generate the user and group with our IDs:&lt;/p&gt; &lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; jtreminio/php:7.2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; USER_ID=1000&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; GROUP_ID=1000&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;userdel &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;getent group www-data &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;groupdel www-data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; groupadd &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; useradd &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; 0755 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; www-data &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data /home/www-data &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;After we delete the user and group, we recreate it with the defined values.&lt;/p&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000&lt;/code&gt; is my user and group ID, and now the container’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; has the same.&lt;/p&gt; &lt;blockquote class=&quot;danger&quot;&gt; &lt;p&gt;Remember to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-l&lt;/code&gt; flag with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useradd&lt;/code&gt;! There is an amazingly fun issue where a high UID value will generate huge log files and freeze your system! &lt;a href=&quot;https://github.com/moby/moby/issues/5419&quot;&gt;Click here for all the juicy details&lt;/a&gt;.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;We also take the time to generate a home directory for our new user. This makes it much easier to have your container perform SSH actions using your host’s SSH keys as long as you bind a volume.&lt;/p&gt; &lt;p&gt;Even though we are done with recreating the user and group, there is a problem: Any files and directories that were owned by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; user will not automatically point to the new ID values. Since the names are just &lt;em&gt;labels&lt;/em&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/php/sessions&lt;/code&gt; is still owned by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;33:33&lt;/code&gt;, not matching our spiffy new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000:1000&lt;/code&gt;!&lt;/p&gt; &lt;p&gt;This is the part where you need to know just enough about the container you want to run that you can pinpoint which files and directories you need to update the owner for.&lt;/p&gt; &lt;p&gt;Thankfully, the rallying cry of containers is to have them do one single thing, which limits the number of places we need to update. In this image, it is not very many at all:&lt;/p&gt; &lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; jtreminio/php:7.2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; USER_ID=1000&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; GROUP_ID=1000&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;userdel &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;getent group www-data &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;groupdel www-data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; groupadd &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; useradd &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; 0755 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; www-data &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data /home/www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--changes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--silent&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-dereference&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;33:33 &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /home/www-data &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /.composer &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /var/run/php-fpm &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /var/lib/php/sessions &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You can figure out what you need by looking at the Dockerfile used to generate the image. For the &lt;a href=&quot;https://github.com/jtreminio/php-docker/blob/master/php7.2/Dockerfile&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jtreminio/php:7.2&lt;/code&gt;&lt;/a&gt; image we can see that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/.composer&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/run/php-fpm&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/php/sessions&lt;/code&gt; directories need updated.&lt;/p&gt; &lt;p&gt;Finally, it does not hurt to be explicit about the user we want to run the container:&lt;/p&gt; &lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; jtreminio/php:7.2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; USER_ID=1000&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; GROUP_ID=1000&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;userdel &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;getent group www-data &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;groupdel www-data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; groupadd &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; useradd &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; 0755 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; www-data &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data /home/www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--changes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--silent&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-dereference&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;33:33 &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /home/www-data &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /.composer &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /var/run/php-fpm &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /var/lib/php/sessions &lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; www-data&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;If you run the container now, you will automatically become the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data&lt;/code&gt; user which now has user/group ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000:1000&lt;/code&gt;. Any files generated by this container will be owned by my host system’s user.&lt;/p&gt; &lt;p&gt;What should we do about the hard-coded IDs, though? Your coworkers may have different IDs on their machines.&lt;/p&gt; &lt;h2 id=&quot;make-it-dynamic&quot;&gt;Make it dynamic&lt;/h2&gt; &lt;p&gt;You can pass arguments when building an image using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--build-arg&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;Update the Dockerfile first:&lt;/p&gt; &lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; jtreminio/php:7.2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; USER_ID&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ARG&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; GROUP_ID&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RUN if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ne&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ne&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; userdel &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;getent group www-data &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;groupdel www-data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; groupadd &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; useradd &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; 0755 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; www-data &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data /home/www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--changes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--silent&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-dereference&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;33:33 &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /home/www-data &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /.composer &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /var/run/php-fpm &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt; /var/lib/php/sessions &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; www-data&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We make both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER_ID&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GROUP_ID&lt;/code&gt; optional. If both are not defined, we skip the whole user delete/recreate process completely. This Dockerfile can now be used for both dev and production purposes.&lt;/p&gt; &lt;p&gt;To build the above image you would do:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker image build &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--build-arg&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--build-arg&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; php_test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Sending build context to Docker daemon 68.1kB Step 1/5 : FROM jtreminio/php:7.2 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 88efaa8cad72 Step 2/5 : ARG USER_ID &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;51d424a6d618 Removing intermediate container 51d424a6d618 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 3feace11551d Step 3/5 : ARG GROUP_ID &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;aaaceff5ee4e Removing intermediate container aaaceff5ee4e &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 9f110b0f92c2 Step 4/5 : RUN &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ne&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ne&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;userdel &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;getent group www-data &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;groupdel www-data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; groupadd &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; 0755 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; www-data &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data /home/www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--changes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--silent&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-dereference&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;33:33 &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; /home/www-data /.composer /var/run/php-fpm /var/lib/php/sessions &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;b516a3ab4600 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/.composer/keys.tags.pub&apos;&lt;/span&gt; from 33:33 to 1000:1000 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/.composer/keys.dev.pub&apos;&lt;/span&gt; from 33:33 to 1000:1000 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/.composer&apos;&lt;/span&gt; from 33:33 to 1000:1000 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/var/run/php-fpm&apos;&lt;/span&gt; from 33:33 to 1000:1000 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/var/lib/php/sessions&apos;&lt;/span&gt; from 33:33 to 1000:1000 Removing intermediate container b516a3ab4600 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; c126c9f07252 Step 5/5 : USER www-data &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;76831280f239 Removing intermediate container 76831280f239 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 5c237c8e46cd Successfully built 5c237c8e46cd Successfully tagged php_test:latest &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Try running Composer with the new image:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; php_test:latest composer require psr/log &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;and check out the result:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-lan&lt;/span&gt; total 24K drwxrwxr-x. 3 1000 1000 4.0K Aug 4 22:50 ./ drwxr-xr-x. 7 1000 1000 4.0K Aug 4 19:31 ../ drwxr-xr-x. 4 1000 1000 4.0K Aug 4 22:50 vendor/ &lt;span class=&quot;nt&quot;&gt;-rw-rw-r--&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; 1 1000 1000 545 Aug 4 22:48 Dockerfile &lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; 1 1000 1000 53 Aug 4 22:50 composer.json &lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; 1 1000 1000 2.1K Aug 4 22:50 composer.lock &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Try creating a file in a protected directory:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker container run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /var/www &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; php_test:latest &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; bash &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;touch /var/lib/php/sessions/foo &amp;amp;&amp;amp; echo &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;?&quot;&lt;/span&gt; 0 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;h2 id=&quot;user-docker-compose&quot;&gt;User Docker Compose&lt;/h2&gt; &lt;p&gt;You can take the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker image build&lt;/code&gt; command and save it to a bash file for easy execution.&lt;/p&gt; &lt;p&gt;However, most folks would rather use Docker Compose for local development.&lt;/p&gt; &lt;p&gt;Here is what your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; might look like:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.2&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;php&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;dockerfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Dockerfile&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$(id -u ${USER})&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$(id -g ${USER})&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${HOME}/.composer:/.composer&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${PWD}:/var/www&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;9000:9000&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Unfortunately, Docker Compose cannot parse commands, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(id -u ${USER})&lt;/code&gt; would not work in the YAML file. Thankfully we can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file to pass values:&lt;/p&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# docker-compose.yml&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.2&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;php&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;dockerfile&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Dockerfile&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${USER_ID:-0}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${GROUP_ID:-0}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${HOME}/.composer:/.composer&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${PWD}:/var/www&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;9000:9000&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;; .env &lt;/span&gt;&lt;span class=&quot;py&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;py&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1000&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;With a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose up -d --build&lt;/code&gt; we are up and running:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose up &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--build&lt;/span&gt; Creating network &lt;span class=&quot;s2&quot;&gt;&quot;temp_default&quot;&lt;/span&gt; with the default driver Building php Step 1/5 : FROM jtreminio/php:7.2 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 88efaa8cad72 Step 2/5 : ARG USER_ID &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;1b406c5797aa Removing intermediate container 1b406c5797aa &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; c61d4e7b14b9 Step 3/5 : ARG GROUP_ID &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;14ac56de9dc8 Removing intermediate container 14ac56de9dc8 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 576ada9d6dbe Step 4/5 : RUN &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ne&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ne&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;userdel &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;getent group www-data &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;groupdel www-data&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; groupadd &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; 0755 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; www-data &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; www-data /home/www-data &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--changes&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--silent&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-dereference&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--recursive&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;33:33 &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GROUP_ID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; /home/www-data /.composer /var/run/php-fpm /var/lib/php/sessions &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;632e67cdc854 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/.composer/keys.tags.pub&apos;&lt;/span&gt; from 33:33 to 1000:1000 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/.composer/keys.dev.pub&apos;&lt;/span&gt; from 33:33 to 1000:1000 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/.composer&apos;&lt;/span&gt; from 33:33 to 1000:1000 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/var/run/php-fpm&apos;&lt;/span&gt; from 33:33 to 1000:1000 changed ownership of &lt;span class=&quot;s1&quot;&gt;&apos;/var/lib/php/sessions&apos;&lt;/span&gt; from 33:33 to 1000:1000 Removing intermediate container 632e67cdc854 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 1bb2e0bbff3c Step 5/5 : USER www-data &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; Running &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;8efe4779c454 Removing intermediate container 8efe4779c454 &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; b3bd40096b94 Successfully built b3bd40096b94 Successfully tagged temp_php:latest Creating temp_php_1 ... &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;We now have a PHP-FPM container running with my system user/group ID &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1000:1000&lt;/code&gt; waiting for connections from Nginx or Apache.&lt;/p&gt; &lt;h2 id=&quot;what-containers-need-this&quot;&gt;What containers need this?&lt;/h2&gt; &lt;p&gt;Not all containers actually require these steps. For simpler images like the official Nginx or Apache that do not generate files you would be interested in editing while in development, you can skip this and let the container run as the normal internal user.&lt;/p&gt; &lt;p&gt;For containers that only fetch dependencies to local disk, like PHP Composer or Node NPM, you can get by with simply passing your user/group ID directly. The container will complain about a user not existing for those values, but will otherwise work fine. Cache directories may need a little bit of work.&lt;/p&gt; &lt;p&gt;Containers with long-running daemons that generate a large amount of files, like parsing SCSS to CSS, will benefit you greatly to implement this concept.&lt;/p&gt; &lt;h2 id=&quot;macos-and-windows-users&quot;&gt;MacOS and Windows users…&lt;/h2&gt; &lt;p&gt;You do not actually need to do any of this. Like, this whole article, you can skip it. Docker does not actually run natively on either of these operating systems, but transparently in a virtual machine.&lt;/p&gt; &lt;p&gt;This virtual machine is run under whatever user installed Docker, which is usually not root on MacOS or administrator on Windows.&lt;/p&gt; &lt;p&gt;While &lt;em&gt;you&lt;/em&gt; do not need this, it is still a good idea to implement it. Your coworkers that run Linux as their main OS will benefit greatly, and if you ever make the switch you will already have your Docker stuff up to date.&lt;/p&gt; &lt;p&gt;You will also only require a single Dockerfile between all your coworkers.&lt;/p&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping it up&lt;/h2&gt; &lt;p&gt;While you &lt;em&gt;can&lt;/em&gt; run all containers as the default internal user, dedicating just a few minutes per image to switch to your user/group ID will help eliminate one fairly big point of frustration.&lt;/p&gt; &lt;p&gt;Overall Docker is an amazing technology but little things like this can mar an otherwise enjoyable experience. Knowing how to smooth the bumps will help make Docker’s promise of Nirvana come true.&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; </description> <pubDate>Sun, 05 Aug 2018 00:00:00 -0500</pubDate> <link>https://jtreminio.com/blog/running-docker-containers-as-current-host-user/</link> <guid isPermaLink="true">https://jtreminio.com/blog/running-docker-containers-as-current-host-user/</guid> <category>docker</category> <category>blog</category> </item> <item> <title>Traefik on Docker for Web Developers</title> <description>&lt;p&gt;Over the last 5+ years I have done all my development on local virtual machines managed by Vagrant and provisioned by Puppet. I even created a fairly well-received FOSS called &lt;a href=&quot;https://puphpet.com&quot;&gt;PuPHPet&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;At the end of 2017 I started really looking into containers, and as of January started working on what will become &lt;a href=&quot;https://puphpet.com&quot;&gt;PuPHPet’s&lt;/a&gt; successor, &lt;a href=&quot;https://dashtainer.com&quot;&gt;Dashtainer&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;While this is not a post on containers in general, or Dashtainer, the thing I will talk about fits in perfectly in the container world and is used heavily in &lt;a href=&quot;https://dashtainer.com&quot;&gt;Dashtainer&lt;/a&gt;.&lt;/p&gt; &lt;h2 id=&quot;problems-with-docker&quot;&gt;Problems with Docker&lt;/h2&gt; &lt;p&gt;While Docker is an amazingly useful tool, it does not come without its own set of problems.&lt;/p&gt; &lt;p&gt;One of the biggest is what I call &lt;em&gt;Docker port dancing&lt;/em&gt;. In Docker, you can bind a port on your host to forward to a container.&lt;/p&gt; &lt;p&gt;For example, you can bind port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt; on host to port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt; on a container, so going to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost&lt;/code&gt; will automatically forward the request to the container. In this way you can bind a webserver (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt;), PHP-FPM (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9000&lt;/code&gt;) and MySQL (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3306&lt;/code&gt;) and very quickly have a complete working environment on your local machine without having to actually install any of those tools, existing only within their containers.&lt;/p&gt; &lt;p&gt;If you ever have only a single project, this may be fine, but once you start spinning up more projects you quickly realize the biggest limitation: you cannot bind a port on host multiple times.&lt;/p&gt; &lt;p&gt;If port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt; is mapped to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-server-A&lt;/code&gt; you must choose another port to bind for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-server-B&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web-server-C&lt;/code&gt;. This can quickly get old because you must remember that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost&lt;/code&gt; goes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:81&lt;/code&gt; goes to&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:82&lt;/code&gt; goes to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt;. Of course the actual port you bind is completely up to you so you can do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8080&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8000&lt;/code&gt; or any unused port on your local machine.&lt;/p&gt; &lt;p&gt;On virtual machines this problem does not really occur because you can assign a static IP address to your servers, and bind it to your system’s hosts file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt;). Containers are ephemeral by nature and do not normally get created on your host’s network but rather private networks with their own random IP addresses within special ranges. However, you &lt;em&gt;must&lt;/em&gt; edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; for every VM you spin up and the list grows with the number of projects you handle.&lt;/p&gt; &lt;p&gt;Træfik solves both of these problems, first by removing the need to use ports in URLs and second by not needing you to edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; at all.&lt;/p&gt; &lt;h2 id=&quot;what-is-træfik&quot;&gt;What is Træfik?&lt;/h2&gt; &lt;p&gt;&lt;a href=&quot;https://traefik.io/&quot;&gt;Træfik&lt;/a&gt; (pronounced &lt;em&gt;traffic&lt;/em&gt;, spelled &lt;em&gt;Traefik&lt;/em&gt; from now on) is a reverse proxy / load balancer similar to HAProxy or Nginx in reverse proxy mode.&lt;/p&gt; &lt;p&gt;Simply put, as a reverse proxy it monitors traffic to specified ports (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt;,&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;443&lt;/code&gt;) and routes traffic to the proper endpoint.&lt;/p&gt; &lt;p&gt;Traefik includes baked-in support for Docker and you can configure it almost fully through flags, with no need for config files. It supports hot-loading and automatically detects changes to environment. Best of all, it supports Let’s Encrypt right out of the box.&lt;/p&gt; &lt;p&gt;Traefik runs as a separate container and this single container can work across any number of separate projects you want. It works by listening to the Docker daemon and reacting to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;labels&lt;/code&gt; you define for each container.&lt;/p&gt; &lt;h2 id=&quot;creating-a-traefik-container&quot;&gt;Creating a Traefik Container&lt;/h2&gt; &lt;p&gt;First create the network Traefik will use:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker network create &lt;span class=&quot;nt&quot;&gt;--driver&lt;/span&gt; bridge traefik_webgateway &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Then create the actual Traefik container:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; traefik_proxy &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 80:80 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 8080:8080 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--restart&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--volume&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--volume&lt;/span&gt; /dev/null:/traefik.toml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; traefik &lt;span class=&quot;nt&quot;&gt;--api&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--docker&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;With the above we,&lt;/p&gt; &lt;ul&gt; &lt;li&gt;tell Traefik to watch to a specific network (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--network traefik_webgateway&lt;/code&gt;) for new containers. Any containers attached to this network can be monitored by Traefik.&lt;/li&gt; &lt;li&gt;tell Traefik to attach itself to the Docker daemon (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--volume /var/run/docker.sock:/var/run/docker.sock&lt;/code&gt;). This is what allows Traefik to listen to the above network and read other containers’ labels.&lt;/li&gt; &lt;li&gt;decline the use of a config file (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--volume /dev/null:/traefik.toml&lt;/code&gt;) to highlight Traefik’s ability to be completely configured via arguments.&lt;/li&gt; &lt;li&gt;bind host’s ports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8080&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p 80:80&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p 8080:8080&lt;/code&gt;) to Traefik.&lt;/li&gt; &lt;li&gt;enable the Traefik GUI dashboard (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--api&lt;/code&gt;)&lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&quot;how-traefik-solves-the-port-dance&quot;&gt;How Traefik Solves the Port Dance&lt;/h2&gt; &lt;p&gt;Traefik can automatically pick up any containers that use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traefik_webgateway&lt;/code&gt; network and reads labels applied.&lt;/p&gt; &lt;p&gt;For example, to spin up a new Nginx container you could do something like:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; some-nginx &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PWD&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;:/usr/share/nginx/html:ro &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.docker.network&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.frontend.rule&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Host:some-nginx.localhost &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.port&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;80 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; nginx:alpine &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Here we tell Traefik that this container’s hostname is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some-nginx.localhost&lt;/code&gt; and it receives traffic on port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;If you open &lt;a href=&quot;http://some-nginx.localhost&quot;&gt;some-nginx.localhost&lt;/a&gt; in Chrome&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; you should see the Nginx container responding.&lt;/p&gt; &lt;p&gt;Using hostnames directly without having to append port numbers to them makes working with Docker containers much easier than having to remember which port goes with which project and which container.&lt;/p&gt; &lt;h2 id=&quot;traefik-gui&quot;&gt;Traefik GUI&lt;/h2&gt; &lt;p&gt;Above I mentioned the Traefik GUI dashboard. It listens on port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8080&lt;/code&gt; so simply open &lt;a href=&quot;http://localhost:8080&quot;&gt;localhost:8080&lt;/a&gt; and you will see all the containers Traefik is currently monitoring for changes.&lt;/p&gt; &lt;h2 id=&quot;non-port-80-example&quot;&gt;Non-port 80 Example&lt;/h2&gt; &lt;p&gt;Some containers do not listen on port 80 by default. This is fine because you can forward any traffic on port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt; on the container’s hostname to the specified port.&lt;/p&gt; &lt;p&gt;For example, MailHog’s GUI sits on port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8025&lt;/code&gt;. You can run it with:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; some-mailhog &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.docker.network&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.frontend.rule&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Host:mailhog.localhost &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.port&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8025 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; mailhog/mailhog &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Even though the container listens to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8025&lt;/code&gt;, opening &lt;a href=&quot;http://mailhog.localhost&quot;&gt;mailhog.localhost&lt;/a&gt; will automatically forward traffic to the proper port.&lt;/p&gt; &lt;p&gt;If you open the Traefik dashboard you will see this new container listed.&lt;/p&gt; &lt;p&gt;When you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker container rm -f some-mailhog&lt;/code&gt; it will automatically be removed.&lt;/p&gt; &lt;h2 id=&quot;built-in-lets-encrypt-support&quot;&gt;Built-in Let’s Encrypt Support&lt;/h2&gt; &lt;p&gt;Once you are ready to go live with your website configuring Traefik to automatically request and maintain a valid Let’s Encrypt SSL certificate is fairly easy!&lt;/p&gt; &lt;p&gt;There are some extra arguments you must define but nothing too foreign:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker container run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; traefik_proxy &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; traefik_webgateway &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 80:80 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 443:443 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 8080:8080 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--restart&lt;/span&gt; always &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--volume&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--volume&lt;/span&gt; /dev/null:/traefik.toml &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--volume&lt;/span&gt; /root/acme.json:/root/acme.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; traefik &lt;span class=&quot;nt&quot;&gt;--docker&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--logLevel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;INFO &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt;.acmelogging &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt;.dnschallenge&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt;.entrypoint&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt;.httpchallenge &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt;.httpChallenge.entryPoint&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt;.onhostrule&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt;.storage&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/root/acme.json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--acme&lt;/span&gt;.email&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;your-email-here@example.com&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--entrypoints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Name:http Address::80 Redirect.EntryPoint:https&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--entrypoints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Name:https Address::443 TLS&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--defaultentrypoints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http,https&quot;&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Since we will handle SSL traffic we add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p 443:443&lt;/code&gt; to the ports list, and since this is for a live server we remove the dashboard (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--api&lt;/code&gt;).&lt;/p&gt; &lt;p&gt;Let’s Encrypt stores its data in files and it requires special permissions before it will work. Unfortunately this is the only thing you cannot do via only CLI arguments. You &lt;strong&gt;must&lt;/strong&gt; create this file and set permissions properly before running the above command.&lt;/p&gt; &lt;p&gt;Simply do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;touch /root/acme.json &amp;amp;&amp;amp; chmod 600 /root/acme.json&lt;/code&gt; and you are all set.&lt;/p&gt; &lt;p&gt;We redirect all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt; traffic to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;443&lt;/code&gt; to encrypt all traffic (as you should already be doing!). Let’s Encrypt cert is generated after it pings &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://your-website.com/.well-known/acme-challenge&lt;/code&gt; to verify ownership.&lt;/p&gt; &lt;p&gt;Traefik will take care of keeping all certs up to date.&lt;/p&gt; &lt;h2 id=&quot;explicitly-disable-traefik-for-non-http-services&quot;&gt;Explicitly Disable Traefik for Non-HTTP Services&lt;/h2&gt; &lt;p&gt;It seems by default Traefik will attempt to generate a Let’s Encrypt cert for all containers, even if the containers are not on the Traefik network.&lt;/p&gt; &lt;p&gt;To prevent Let’s Encrypt errors from breaking your build, you &lt;em&gt;must&lt;/em&gt; explicitly disable Traefik on containers that do not need a cert, like a PHP-FPM container.&lt;/p&gt; &lt;p&gt;It is easy enough:&lt;/p&gt; &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; php-fpm &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--network&lt;/span&gt; private &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--label&lt;/span&gt; traefik.enable&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt; your-php/image &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;The above is not required on dev since you will not be generating SSL certs.&lt;/p&gt; &lt;h2 id=&quot;limitations&quot;&gt;Limitations&lt;/h2&gt; &lt;p&gt;Traefik only handles HTTP/HTTPS traffic. It cannot currently handle TCP/UDP.&lt;/p&gt; &lt;p&gt;This means any database containers cannot be aliased to a hostname, and you will need to do the port dance on these services.&lt;/p&gt; &lt;p&gt;This is an unfortunate limitation, and &lt;a href=&quot;https://github.com/containous/traefik/issues/10&quot;&gt;the GitHub issue&lt;/a&gt; has been open for a while without much movement.&lt;/p&gt; &lt;p&gt;This does &lt;em&gt;not&lt;/em&gt; mean your services cannot communicate with each other via normal Docker hostnames. For example, a service named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php-fpm&lt;/code&gt; on network &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foobar&lt;/code&gt; can still communicate with a service named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysql&lt;/code&gt; on the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foobar&lt;/code&gt; network, using the hostname &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysql&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;You, however, cannot access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysql&lt;/code&gt; from your host via a GUI like Sequel Pro or MySQL Administrator. You can either bind a port from host to container, or use something like Adminer.&lt;/p&gt; &lt;h2 id=&quot;via-docker-composeyml&quot;&gt;Via docker-compose.yml&lt;/h2&gt; &lt;p&gt;If you prefer to try this out using Docker-Compose, create the following 3 files:&lt;/p&gt; &lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;; traefik/.env &lt;/span&gt;&lt;span class=&quot;py&quot;&gt;COMPOSE_PROJECT_NAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;traefik&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# traefik/docker-compose.yml&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.2&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;--api --docker --docker.domain=docker.localhost --logLevel=DEBUG&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;webgateway&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;80:80&quot;&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;8080:8080&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/dev/null:/traefik.toml&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;webgateway&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;driver&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bridge&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# app/docker-compose.yml&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.2&apos;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;private&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik_webgateway&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;nginx&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx:alpine&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.backend=nginx&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.docker.network=traefik_webgateway&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.frontend.rule=Host:some-nginx.localhost&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.port=80&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;mailhog&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mailhog/mailhog&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;labels&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.backend=mailhog&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.docker.network=traefik_webgateway&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.frontend.rule=Host:mailhog.localhost&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik.port=8025&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;networks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;public&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose up -d&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traefik&lt;/code&gt; directory, then the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app&lt;/code&gt; directory.&lt;/p&gt; &lt;h2 id=&quot;shameless-plug&quot;&gt;Shameless Plug&lt;/h2&gt; &lt;p&gt;If you are new to the world of containers, I have created a FOSS called &lt;a href=&quot;https://dashtainer.com&quot;&gt;Dashtainer&lt;/a&gt; to help you quickly generate and run containers tailored to your app’s requirements.&lt;/p&gt; &lt;p&gt;If you give it a go, I would love to hear from you!&lt;/p&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping it up&lt;/h2&gt; &lt;p&gt;Docker has brought containers to the mainstream, but little gotchas like port dancing can be frustrating to new users. Hopefully with this small tutorial you are able to get up and running and get back to developing your &lt;em&gt;Make the World a Better Place&lt;/em&gt; app.&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; &lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt; &lt;ol&gt; &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt; &lt;p&gt;I specify Chrome because as of this writing I believe it is the only browser that will always resolve any *.localhost to the loopback interface. This means you do not need to touch the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; file, the hostname will work automatically. If you do not want to use Chrome then you must install dnsmasq on MacOS or Acrylic on Windows. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;/div&gt; </description> <pubDate>Tue, 31 Jul 2018 00:00:00 -0500</pubDate> <link>https://jtreminio.com/blog/traefik-on-docker-for-web-developers/</link> <guid isPermaLink="true">https://jtreminio.com/blog/traefik-on-docker-for-web-developers/</guid> <category>webdev</category> <category>docker</category> <category>lets-encrypt</category> <category>blog</category> </item> <item> <title>Developing at Full Speed with Xdebug</title> <description>&lt;h2 id=&quot;a-quick-history&quot;&gt;A quick history&lt;/h2&gt; &lt;p&gt;&lt;em&gt;ed: If you want to jump right to the solution, jump ahead to &lt;strong&gt;&lt;a href=&quot;#nginx-map&quot;&gt;Nginx map&lt;/a&gt;&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;&lt;a href=&quot;https://github.com/docker/for-mac/issues/2659&quot;&gt;Docker&lt;/a&gt; &lt;a href=&quot;https://github.com/docker/for-mac/issues/2707&quot;&gt;for&lt;/a&gt; &lt;a href=&quot;https://forums.docker.com/t/painfully-slow/32078&quot;&gt;Mac&lt;/a&gt; &lt;a href=&quot;https://spin.atomicobject.com/2017/06/20/docker-mac-overcoming-slow-volumes/&quot;&gt;is&lt;/a&gt; &lt;a href=&quot;https://medium.com/@TomKeur/how-get-better-disk-performance-in-docker-for-mac-2ba1244b5b70&quot;&gt;very&lt;/a&gt; &lt;a href=&quot;https://medium.freecodecamp.org/speed-up-file-access-in-docker-for-mac-fbeee65d0ee7&quot;&gt;slow&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;It is so slow that I purchased a new Dell XPS laptop and for the first time in 6 years am now using a non-MacOS (Fedora) machine as my daily driver.&lt;/p&gt; &lt;p&gt;Not everyone has the luxury of switching their OS, though, and they are stuck on slow Docker.&lt;/p&gt; &lt;p&gt;A normal Symfony 2.4 application will commonly see between 400ms-750ms response times in development mode, &lt;em&gt;without&lt;/em&gt; Xdebug installed. If Xdebug is activated response times of 1,200ms+ can frustrate even the most devoted Xdebug fan.&lt;/p&gt; &lt;p&gt;Before switching to Fedora I tried everything I could to minimize Xdebug’s impact on performance. &lt;a href=&quot;/blog/xdebug-and-you-why-you-should-be-using-a-real-debugger/&quot;&gt;I have been using Xdebug for several years&lt;/a&gt; and while I always felt the benefits of Xdebug far out-weighed the extra slowness, I knew there had to be a better way.&lt;/p&gt; &lt;h2 id=&quot;how-xdebug-decides-to-run&quot;&gt;How Xdebug decides to run&lt;/h2&gt; &lt;p&gt;There are several ways of enabling Xdebug for a specific session. The more popular ways are using cookies, like those generated by &lt;a href=&quot;https://www.jetbrains.com/phpstorm/marklets/&quot;&gt;PhpStorms bookmarklets&lt;/a&gt;. You can also kick Xdebug off via CLI to debug command line scripts without a web portion.&lt;/p&gt; &lt;p&gt;All methods share the same requirement: Xdebug must be installed and loaded on the system to work.&lt;/p&gt; &lt;p&gt;The all also let the PHP layer of your application decide whether to enable Xdebug for the current session.&lt;/p&gt; &lt;p&gt;If you take a look at PhpStorm’s bookmarklets, the code is actually quite simple:&lt;/p&gt; &lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;javascript&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:(&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/** @version 0.5.2 */&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cookie&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;XDEBUG_SESSION=&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;xdebug&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;;path=/;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;})()&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;It simply sets a cookie named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XDEBUG_SESSION&lt;/code&gt; and sets a value to it. By default PhpStorm wants to use… &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phpstorm&lt;/code&gt;, but I always set it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xdebug&lt;/code&gt; as above.&lt;/p&gt; &lt;p&gt;Knowing this,would it be possible to move the decision one layer above, out of PHP’s hands? Instead of PHP reading for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XDEBUG_SESSION&lt;/code&gt; cookie and acting on it, do it somewhere else.&lt;/p&gt; &lt;h2 id=&quot;docker-configuration&quot;&gt;Docker configuration&lt;/h2&gt; &lt;p&gt;A normal Docker PHP application looks like:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;1 webserver like Nginx&lt;/li&gt; &lt;li&gt;1 PHP container with Xdebug installed&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;The problem so far has been that the PHP side of things is slowing everything down, due to Xdebug being installed.&lt;/p&gt; &lt;p&gt;What if instead of the above, we had:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;1 webserver like Nginx&lt;/li&gt; &lt;li&gt;1 PHP container with Xdebug installed (named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php_xdebug&lt;/code&gt;)&lt;/li&gt; &lt;li&gt;1 PHP container without Xdebug installed (named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php&lt;/code&gt;)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;The trick here is making the decision to invoke Xdebug before PHP becomes aware that a new request is being processed. Thankfully, Nginx’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; can help us tremendously!&lt;/p&gt; &lt;h2 id=&quot;nginx-map&quot;&gt;Nginx map&lt;/h2&gt; &lt;p&gt;Nginx maps are quite simple, and what I came up with to handle the Xdebug cookie requirement is this:&lt;/p&gt; &lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cookie_XDEBUG_SESSION&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$my_fastcgi_pass&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;php&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;xdebug&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;php_xdebug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;In PHP the above would look like this:&lt;/p&gt; &lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$cookie_XDEBUG_SESSION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;xdebug&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$my_fastcgi_pass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;php_xdebug&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$my_fastcgi_pass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;php&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$cookie_XDEBUG_SESSION&lt;/code&gt; is the cookie set by PhpStorm’s bookmarklet, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$my_fastcgi_pass&lt;/code&gt; is the server Nginx will use for this request.&lt;/p&gt; &lt;p&gt;A full Symfony 3 Nginx config would look like this:&lt;/p&gt; &lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 &lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# default Docker DNS server&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;resolver&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;127&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.0.0.11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cookie_XDEBUG_SESSION&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$my_fastcgi_pass&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;php&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;xdebug&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;php_xdebug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;*:8080&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;default_server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;server_name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/var/www/public&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;autoindex&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;try_files&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$uri&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/app.php&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$is_args$args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;^/(app_dev|config)\.php(/|$)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$path_info&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$fastcgi_path_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;fastcgi_pass&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$my_fastcgi_pass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;fastcgi_split_path_info&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;^(.+&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.php)(/.*)&lt;/span&gt;$&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fastcgi_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;fastcgi_param&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;fastcgi_param&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DOCUMENT_ROOT&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$realpath_root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;^/app\.php(/|$)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$path_info&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$fastcgi_path_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;fastcgi_pass&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;php&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;9000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;fastcgi_split_path_info&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;^(.+&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.php)(/.*)&lt;/span&gt;$&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fastcgi_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;fastcgi_param&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;fastcgi_param&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;DOCUMENT_ROOT&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$realpath_root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;internal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;\.php$&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt; &lt;p&gt;You can see how we’re actually using the result of the map at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fastcgi_pass $my_fastcgi_pass:9000;&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;The Nginx part is simple, but critical. Nginx reads the cookie and sends traffic either to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php&lt;/code&gt; server or the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php_xdebug&lt;/code&gt; server. There is nothing magical about this, and PHP is not aware that a request is being made until Nginx has made the decision to route the request to its server. The other server would remain blissfully unaware of the request.&lt;/p&gt; &lt;h2 id=&quot;good-solution-or-hacky-workaround&quot;&gt;Good solution or hacky workaround?&lt;/h2&gt; &lt;p&gt;Your normal workflow with Xdebug would look like this:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;You enable the Xdebug cookie in your browser&lt;/li&gt; &lt;li&gt;You create a breakpoint and enable the listener in your IDE&lt;/li&gt; &lt;li&gt;You load the page in your browser, Xdebug and your IDE connect and stop at the breakpoint defined.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;With my Nginx map solution, your workflow &lt;em&gt;remains exactly the same&lt;/em&gt;. It does &lt;em&gt;not&lt;/em&gt; change!&lt;/p&gt; &lt;p&gt;&lt;em&gt;But&lt;/em&gt;, instead of Xdebug slowing down every single request, whether you have set a breakpoint or not, &lt;em&gt;whenever you do not have the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XDEBUG_SESSION&lt;/code&gt; cookie enabled, you are hitting the non-Xdebug server!&lt;/em&gt; In real-world testing this means that the majority of my requests are now routed directly to the PHP server that does not have Xdebug installed.&lt;/p&gt; &lt;p&gt;On my MacBook Pro requests see a normal response time of sub-500ms. This is still not great (because, Docker), but is worlds better than &lt;em&gt;every single request&lt;/em&gt; being 1,000ms+!&lt;/p&gt; &lt;p&gt;When I &lt;em&gt;do&lt;/em&gt; want to enable debugging, I set my breakpoint, enable the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;XDEBUG_SESSION&lt;/code&gt; cookie by using PhpStorm’s bookmarklets, and Nginx routes my request to the PHP server with Xdebug installed.&lt;/p&gt; &lt;p&gt;When this happens, I have a breakpoint enabled and will be looking at my IDE, so response times do not matter at all. The biggest (and only) painpoint that Xdebug introduces is completely eliminated.&lt;/p&gt; &lt;h2 id=&quot;what-took-so-long&quot;&gt;What took so long?&lt;/h2&gt; &lt;p&gt;Until 6 months ago I was using Vagrant virtual machines for all my development. My FOSS &lt;a href=&quot;https://puphpet.com&quot;&gt;PuPHPet&lt;/a&gt; is evidence that I was all-in on VMs.&lt;/p&gt; &lt;p&gt;However, since completely making the switch to Docker containers, my workflow has changed for the better, even with the slowness Docker for Mac shows.&lt;/p&gt; &lt;p&gt;Docker containers have the benefit of being incredibly light-weight compared to virtual machines, and this makes spinning up two PHP containers for each of my projects completely feasible.&lt;/p&gt; &lt;p&gt;This solution would have been much heavier on virtual machines. On containers it is easier, simpler and faster.&lt;/p&gt; &lt;h2 id=&quot;downsides&quot;&gt;Downsides&lt;/h2&gt; &lt;p&gt;I have not found any downsides to this technique. If you use file-based sessions (why?) then you’ll have to share the volumes between the PHP containers.&lt;/p&gt; &lt;h2 id=&quot;test-it-out&quot;&gt;Test it out&lt;/h2&gt; &lt;p&gt;If you want to try this out in a real-world project, and are not familiar enough with Docker to do so quickly, then you can clone my new &lt;a href=&quot;https://github.com/jtreminio/dashtainer&quot;&gt;FOSS Dashtainer repo&lt;/a&gt; and run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/init&lt;/code&gt; script to get you up and running in a single step.&lt;/p&gt; &lt;p&gt;On a very related note, if you want to get started with Docker but need some help for the initial hurdles, my &lt;a href=&quot;https://dashtainer.com&quot;&gt;FOSS Dashtainer&lt;/a&gt; is the successor to my other &lt;a href=&quot;https://puphpet.com&quot;&gt;FOSS PuPHPet&lt;/a&gt; and is aimed at developers who need an introduction to the strange new world of containers.&lt;/p&gt; &lt;p&gt;&lt;em&gt;edit&lt;/em&gt;: I have created a barebones repo for you to test this out. Clone my &lt;a href=&quot;https://github.com/jtreminio/blog&quot;&gt;jtreminio/blog&lt;/a&gt; repo and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init&lt;/code&gt; inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;developing-at-full-speed-with-xdebug&lt;/code&gt; directory.&lt;/p&gt; &lt;h2 id=&quot;bonus-identical-configurations-in-windows-macos-and-linux&quot;&gt;Bonus: Identical configurations in Windows, MacOS and Linux&lt;/h2&gt; &lt;p&gt;Docker has a special hostname, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;host.docker.internal&lt;/code&gt; that points to the host machine. Unfortunately this only works on Windows and MacOS, Docker on Linux does not have this feature.&lt;/p&gt; &lt;p&gt;This makes setting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xdebug.remote_host&lt;/code&gt; value in the PHP INI annoying becuase not all of your team can simply spin up your Docker container and have Xdebug connect back to the host automatically if they are not on Linux.&lt;/p&gt; &lt;p&gt;There is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xdebug.remote_connect_back&lt;/code&gt; which does just that, though, except it has always been a double-edged sword because it would attempt to connect back for every single request. My solution above resolves this completely!&lt;/p&gt; &lt;p&gt;Simply set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xdebug.remote_connect_back=1&lt;/code&gt; in your INI and Xdebug will connect back to your IDE everytime you enabled the session using PhpStorm’s bookmarklets.&lt;/p&gt; &lt;h2 id=&quot;wrapping-it-up&quot;&gt;Wrapping it up&lt;/h2&gt; &lt;p&gt;With this small technique you gain the ability to develop at full speed without sacrificing modern tools like the incredible Xdebug. Usually with these sorts of things there’s also some downside, but all I see is positives.&lt;/p&gt; &lt;p&gt;Until next time, this is Señor PHP Developer Juan Treminio wishing you adios!&lt;/p&gt; </description> <pubDate>Mon, 23 Jul 2018 00:00:00 -0500</pubDate> <link>https://jtreminio.com/blog/developing-at-full-speed-with-xdebug/</link> <guid isPermaLink="true">https://jtreminio.com/blog/developing-at-full-speed-with-xdebug/</guid> <category>webdev</category> <category>server</category> <category>php</category> <category>xdebug</category> <category>docker</category> <category>blog</category> </item> </channel> </rss>