<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Martin Pham</title>
    <description>The latest articles on DEV Community by Martin Pham (@martinpham).</description>
    <link>https://dev.to/martinpham</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F299083%2Feef7eda8-744e-4c1e-90cb-be10074747fe.jpeg</url>
      <title>DEV Community: Martin Pham</title>
      <link>https://dev.to/martinpham</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/martinpham"/>
    <language>en</language>
    <item>
      <title>Fully type-safe integration between your Laravel backend and your frontend</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Tue, 12 Aug 2025 11:55:13 +0000</pubDate>
      <link>https://dev.to/martinpham/fully-type-safe-integration-between-your-laravel-backend-and-your-frontend-5bj2</link>
      <guid>https://dev.to/martinpham/fully-type-safe-integration-between-your-laravel-backend-and-your-frontend-5bj2</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; If you've ever wanted fully type-safe integration between your Laravel backend and your frontend (whether that's API consumers or Inertia.js apps), the &lt;code&gt;laravel-type-generator&lt;/code&gt; package might save you a ton of time. I just released this package that I've been working on for a while.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;I wanted a completely type-safe workflow between Laravel and the frontend — so backend changes wouldn't silently break my frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;The concept is simple: use OpenAPI to describe your API documentation, then feed it to tools like Orval to automatically generate TypeScript clients and types.&lt;/p&gt;

&lt;p&gt;I tried several existing packages, but none gave me the accuracy or automation I was looking for. So I decided to build my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;laravel-type-generator&lt;/code&gt; package scans your Laravel routes, controllers, models, DTOs, and their properties, methods, docblocks, and PHP source code. It then automatically generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript types from your Laravel routes&lt;/li&gt;
&lt;li&gt;OpenAPI specifications for API documentation&lt;/li&gt;
&lt;li&gt;Swagger UI to visualize and interact with your OpenAPI spec&lt;/li&gt;
&lt;li&gt;Inertia.js type generation&lt;/li&gt;
&lt;li&gt;Accurate handling of pagination, collections, and complex return types&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;(More transformers are coming soon!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Think of it as a bridge that keeps your Laravel backend and frontend perfectly synchronized with types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require martinpham/laravel-type-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publish the config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"type-generator"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan types:generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;The package is flexible. In the config file, you can customize what gets generated and where it goes. Here's a practical example showing how to generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An OpenAPI specification JSON file for routes matching &lt;code&gt;/_api/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;TypeScript types for Inertia.js routes and template variables, for controllers matching &lt;code&gt;App\Http\Controllers\Web\*&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'route_prefixes'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'uri:_api'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'output'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resource_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'api/openapi.json'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'MartinPham\TypeGenerator\Writers\OpenAPI\OpenAPI'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'options'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'openapi'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'3.0.2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'OpenAPI'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'version'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'1.0.0'&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'controller:App\Http\Controllers\Web\\'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'output'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resource_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'js/types/inertia-routes.ts'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'class'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'MartinPham\TypeGenerator\Writers\Inertia\Inertia'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example: How Your Controller Becomes Typed API Specs
&lt;/h2&gt;

&lt;p&gt;Let's say you have a simple controller method like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlaygroundApiController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Here's what happens under the hood:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The package analyzes the &lt;code&gt;findUser&lt;/code&gt; method's input parameter (&lt;code&gt;User $user&lt;/code&gt;) and return type (&lt;code&gt;User&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;It automatically generates an OpenAPI operation for this route with &lt;code&gt;$user&lt;/code&gt; as a parameter.&lt;/li&gt;
&lt;li&gt;It creates a &lt;code&gt;User&lt;/code&gt; schema for both request and response types.&lt;/li&gt;
&lt;li&gt;Since your User model has relationships (like Address and Country), it recursively generates schemas for those related models too — so your OpenAPI spec and TypeScript types reflect the complete data structure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means your frontend gets fully typed models, including nested relationships, without any extra manual work!&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Seamless Integration with Orval for Type-Safe API Clients &amp;amp; Inertia template
&lt;/h2&gt;

&lt;p&gt;Once &lt;code&gt;laravel-type-generator&lt;/code&gt; creates your OpenAPI spec, you can feed it directly into Orval to automatically generate TypeScript types and API clients.&lt;/p&gt;

&lt;p&gt;Here's what Orval would produce from the generated spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;address&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;street&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;country&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Country&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Country&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;code&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiPlaygroundUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;AxiosRequestConfig&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AxiosResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/_api/playground/findUser/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What this means for you:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your frontend knows exactly what data to expect — including nested objects like Address and Country&lt;/li&gt;
&lt;li&gt;Your API calls are fully typed, with correct parameter and response types&lt;/li&gt;
&lt;li&gt;No more guesswork or brittle &lt;code&gt;any&lt;/code&gt; types when consuming your Laravel backend APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a tight feedback loop between your backend and frontend, boosting developer confidence and reducing bugs from mismatched data structures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inertia template
&lt;/h3&gt;

&lt;p&gt;On Inertia side, you will receive exactly the types which were returned by the route&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Full Support for Various Data Types
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;laravel-type-generator&lt;/code&gt; isn't limited to just Eloquent models. It also supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plain PHP classes&lt;/li&gt;
&lt;li&gt;Laravel Data objects (like from Spatie's laravel-data package)&lt;/li&gt;
&lt;li&gt;API Resources&lt;/li&gt;
&lt;li&gt;Your own custom-defined docblocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To give you more control over the generated OpenAPI spec, you can use special docblock annotations in your controller methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@id&lt;/code&gt; — Overrides the default operationId (otherwise it's derived from the route name)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@tag&lt;/code&gt; — Adds tags to group and organize your API operations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@throws&lt;/code&gt; — Specify errors that could be thrown during the call&lt;/li&gt;
&lt;li&gt;Method descriptions are also captured in the OpenAPI document&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This flexibility lets you define complex return types with complete accuracy. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @return Collection&amp;lt;int, User&amp;gt;
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;allUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Collection&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 *
 * @return array{
 *     requestedAt: DateTime | null,
 *     users: User[],
 *     cars: Collection&amp;lt;int, Car&amp;gt;,
 *     carsWithPagination: LengthAwarePaginator&amp;lt;int, Car&amp;gt;,
 *     nestedObject: array{
 *         evenDeeper: array{
 *             message: string
 *         }
 *     }
 * }
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;manything&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Address&lt;/span&gt; &lt;span class="nv"&gt;$address&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Here's what's happening:
&lt;/h3&gt;

&lt;p&gt;The package reads the docblocks and uses PHP's native type hints to generate detailed, nested TypeScript types and OpenAPI schemas.&lt;/p&gt;

&lt;p&gt;Collections, paginated results, arrays with nested objects — everything gets parsed and converted correctly.&lt;/p&gt;

&lt;p&gt;This means you can describe even very complex API responses in your controller methods and get fully typed clients on the frontend without any manual synchronization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specifying Request Parameters
&lt;/h2&gt;

&lt;p&gt;Your API request parameters can come from different places, and &lt;code&gt;laravel-type-generator&lt;/code&gt; supports all of them to generate accurate types:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Route Path Parameters
&lt;/h3&gt;

&lt;p&gt;Simply define parameters in your route URL, like &lt;code&gt;/users/{user}/&lt;/code&gt;. These will be automatically detected and typed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. FormRequest Subclasses
&lt;/h3&gt;

&lt;p&gt;You can define a FormRequest class with typed properties and validation rules. The package inspects both your docblock properties and validation rules to generate the parameter types.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @property UploadedFile $file
 * @property string $nickname
 * @property array{
 *     name: string,
 *     age: int
 * } $person
 */&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GreetingRequest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FormRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'file'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Docblock Annotations on Controller Methods
&lt;/h3&gt;

&lt;p&gt;You can also specify request shapes directly in your controller method's docblocks using generics and param tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * Greet
 *
 * Generates a greeting message.
 *
 * @id greeting
 * @param Request&amp;lt;array{
 *     user_id: int
 * }&amp;gt; $request
 * @param string $name The receiver's nickname.
 * @return Message|null The greeting message
 * @throws InvalidName Invalid name provided
 * @throws \InvalidArgumentException Invalid args provided
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Message&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the package exactly what your request parameters should look like, allowing for precise type generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development &amp;amp; Feedback
&lt;/h2&gt;

&lt;p&gt;This package is under very active development, and I’m constantly working to improve it.&lt;/p&gt;

&lt;p&gt;I hope you find it useful! Feel free to try it out, and please suggest any features or improvements you’d like to see.&lt;/p&gt;

&lt;p&gt;Your feedback helps make this tool better for everyone!&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>inertia</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building the perfect automated workflows for  Blockchain's development</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Mon, 22 Nov 2021 13:36:10 +0000</pubDate>
      <link>https://dev.to/martinpham/building-the-perfect-automated-workflows-blockchains-development-4p61</link>
      <guid>https://dev.to/martinpham/building-the-perfect-automated-workflows-blockchains-development-4p61</guid>
      <description>&lt;h3&gt;
  
  
  Build workflow for Blockchain's development
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;TLDR: We were having a bad time with Blockchain smartcontract's development, and fortunately we had found solutions to save our asses. We'd like to share it with you, so maybe it could be useful to you also.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Welcome to the Blockchain&lt;/li&gt;
&lt;li&gt;Problems with our development/deployment&lt;/li&gt;
&lt;li&gt;Writing Upgradable smartcontracts&lt;/li&gt;
&lt;li&gt;Automatic deployment to the blockchains&lt;/li&gt;
&lt;li&gt;Problem with the Truffle's migration process&lt;/li&gt;
&lt;li&gt;Our completed workflows&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Blockchain is fun!
&lt;/h3&gt;

&lt;p&gt;It's a huge public database (chain of data blocks), which is being shared across many computers. Everyone can query data inside it, or add a new record by sending transactions. Everything is stored historically, so you can trace the changes easily.&lt;br&gt;
With ethereum chains, it's even funnier with SmartContract - a program that runs on chains. Developers can write and compile a program, and upload it into the chain. Then everyone can run it to read its data, update its data with transactions, or even use it to run another program. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tips&lt;/strong&gt;: You can use this tool &lt;a href="https://contract.mph.am/" rel="noopener noreferrer"&gt;Smartcontract UI&lt;/a&gt; to interact with smartcontracts easily.&lt;/p&gt;

&lt;p&gt;We were working on many projects based on blockchains, mainly in decentralized finance. Many of them are programs (written with Solidity) that involve managing users' account balance, allowing users to trade their assets, or to stake their assets to the liquidity pools and gain interests. &lt;/p&gt;
&lt;h3&gt;
  
  
  Problems with our development/deployment
&lt;/h3&gt;

&lt;p&gt;However, we were usually facing a big problem: Smartcontracts in ethereum chains are immutable. It means once you deployed it on the chain, there is no way to change it. It works just like a contract we have in real life: you surely won't change the contract's contents once you signed it, if you want to, you'd need to have another contract. Same in ethereum chains, if we make changes in the contract code, we'd need to deploy it again to a new smartcontract.&lt;/p&gt;

&lt;p&gt;And since a smartcontract has its data, when we deployed to a new smartcontract, we'd need to migrate all data from the old contract to the new one, while keeping support on both contracts until all users moved to the new contract.&lt;/p&gt;

&lt;p&gt;Another problem we were having is the contract's deployment. After finishing the code and passing all tests on the local environment, we rely on someone to deploy it to the test chains so we can test its communication with other contracts. And since the contract's address will be changed each deployment, it's a really big pain for us to update all the addresses on each deployment. &lt;/p&gt;

&lt;p&gt;After the contract's deployment, we have another problem with the interaction with our contracts. We provide ABIs (Application Binary Interface) to other people, so they can use them to interact with our contracts. It's also needed for our team to write applications that interact with contracts, so we'll need to keep them always updated with our deployed contracts.&lt;/p&gt;

&lt;p&gt;SmartContract's development is fun, but its deployment was a pain for us for a long time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Writing Upgradable smartcontracts with OpenZeppelin
&lt;/h3&gt;

&lt;p&gt;We've decided to improve our deployment process. Thanks to &lt;a href="https://openzeppelin.com/" rel="noopener noreferrer"&gt;OpenZeppelin&lt;/a&gt;, we're now able to upgrade our contracts smoothly. &lt;br&gt;
When we deploy the contract on the first time, OpenZeppelin will create a Proxy Contract, points it to our actual contract, and finally, it deploys all contracts. Later, every time we make changes to the contract's code, OpenZeppelin will deploy it, and points the Proxy Contract to the newly deployed contract, keeping the old contract's state. Now we don't have to worry about the migration between contract's deployments. Our users and devs always connect with the Proxy Contract address, which points to the latest deployed contract.&lt;br&gt;
It was a good change, that helped us during the release process. However, we were still having other problems with the deployment: We'd still need someone to build the contracts and deploy them to the chains. It requires access to our deployer's wallet, and we cannot give the wallet access to everyone in the team.&lt;/p&gt;
&lt;h3&gt;
  
  
  Automatic deployment with Github Actions and Workflows
&lt;/h3&gt;

&lt;p&gt;After many tries, we finally integrate our build &amp;amp; deployment process with Github workflows. We also added some tweaks (like caching dependencies for faster build in future, configuring Truffle environment correctly, having wallet's private key in Github's secret,..)&lt;br&gt;
Our deployment is automatic now! Every time we merge features into a development branch, a workflow will be triggered to build the contracts and upload them into the test blockchain. And when they are ready for production, we just need to tag the version. Github will deploy the contracts into the production blockchain.&lt;br&gt;
We also release the contract's built artifact to Github release page every time we release the contracts to production, along with the prerelease of the contract's ABI on each push to the development branches. In this way, our users and devs can always update the contract's ABI quickly and easily.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Fmainnet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Fmainnet.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Memorize Truffle's migration process
&lt;/h3&gt;

&lt;p&gt;Everything seems OK, but actually, we're still missing a piece: We use &lt;strong&gt;&lt;a href="https://github.com/actions/cache" rel="noopener noreferrer"&gt;actions/cache@v2&lt;/a&gt;&lt;/strong&gt; to cache the Truffle's build. So in the next deployment, we can continue the migration without doing it from the beginning.&lt;br&gt;
However, the cache will be removed after some inactivity time, and when it's removed, or if there is some problem in the cache (cause of a wrongly configured deployment), our migration process will be restarted from the beginning. Therefore, all the proxy contracts will be changed. We will need to notice our users and devs, and migrate all contract's state.&lt;br&gt;
Again, we had to try other approaches, and finally, we found a solution: Saving the Truffle's build on a deployed branch. So for each deployment, instead of taking the previous build from the cache, we will pull it from the deployed branch, and continue with it. It has some more advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Truffle's build will be there always&lt;/li&gt;
&lt;li&gt;We can inspect the Truffle's build if there was any problem with the deployment&lt;/li&gt;
&lt;li&gt;We can also customize the deployed branch to give more informations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Fdeployed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Fdeployed.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Submission Category: DIY Deployments
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ActionsHackathon21" rel="noopener noreferrer"&gt;
        ActionsHackathon21
      &lt;/a&gt; / &lt;a href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain" rel="noopener noreferrer"&gt;
        deploy-upgradable-smartcontract-to-blockchain
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Deploy upgradable smartcontracts to blockchain
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Build documentations&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This project follows the DEV.to &lt;a href="https://dev.to/devteam/join-us-for-the-2021-github-actions-hackathon-on-dev-4hn4" rel="nofollow"&gt;#ActionsHackathon21&lt;/a&gt; hackathon.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Use GitHub Actions and Workflows to build and deploy upgradable smartcontracts into the ethereum blockchains. After its deployment, the contract's ABI will be released, and the artifacts will be saved into a deployed branch.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/raw/main/docs/testnet.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Ftestnet.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/raw/main/docs/mainnet.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Fmainnet.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Check the complete workflow here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deploy contracts to testnet on each push on development branches (&lt;a href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain.github/workflows/migrate-to-testnet.yml" rel="noopener noreferrer"&gt;migrate-to-testnet.yml&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Deploy contracts to mainnet on each push on tag (&lt;a href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain.github/workflows/migrate-to-mainnet.yml" rel="noopener noreferrer"&gt;migrate-to-mainnet.yml&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/raw/main/docs/deployed.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Fdeployed.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Actions used&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actions/checkout" rel="noopener noreferrer"&gt;actions/checkout@v2&lt;/a&gt;&lt;/strong&gt; To checkout the source code from the repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actions/cache" rel="noopener noreferrer"&gt;actions/cache@v2&lt;/a&gt;&lt;/strong&gt; To cache the dependencies, allow us to re use them for future builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/marvinpinto/action-automatic-releases" rel="noopener noreferrer"&gt;marvinpinto/action-automatic-releases@latest&lt;/a&gt;&lt;/strong&gt; To release your build to Github Release page&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/raw/main/docs/release.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Frelease.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Configurations&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;You can config the branch postfix which holds the development's artifacts with the &lt;code&gt;DEPLOY_BRANCH_POSTFIX&lt;/code&gt; variable.&lt;/li&gt;
&lt;li&gt;You can also configure the development branches which you want to deploy to testnet, with &lt;code&gt;branches&lt;/code&gt; key.&lt;/li&gt;
&lt;li&gt;To config the blockchain you want to deploy to, use…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;README: &lt;a href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/blob/main/README.md" rel="noopener noreferrer"&gt;https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/blob/main/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Workflow file (development): &lt;a href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/blob/main/.github/workflows/migrate-to-testnet.yml" rel="noopener noreferrer"&gt;https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/blob/main/.github/workflows/migrate-to-testnet.yml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Workflow file (release): &lt;a href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/blob/main/.github/workflows/migrate-to-mainnet.yml" rel="noopener noreferrer"&gt;https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/blob/main/.github/workflows/migrate-to-mainnet.yml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: &lt;a href="https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/blob/main/COPYING" rel="noopener noreferrer"&gt;https://github.com/ActionsHackathon21/deploy-upgradable-smartcontract-to-blockchain/blob/main/COPYING&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can config the branch postfix which holds the development's artifacts with the &lt;code&gt;DEPLOY_BRANCH_POSTFIX&lt;/code&gt; variable. &lt;/li&gt;
&lt;li&gt;You can also configure the development branches which you want to deploy to testnet, with &lt;code&gt;branches&lt;/code&gt; key.&lt;/li&gt;
&lt;li&gt;To config the blockchain you want to deploy to, use the &lt;code&gt;WALLET_SECRET&lt;/code&gt; , &lt;code&gt;RPC&lt;/code&gt;, &lt;code&gt;NETWORK_ID&lt;/code&gt; and &lt;code&gt;CONFIRMATIONS&lt;/code&gt; variables
&lt;strong&gt;Important!&lt;/strong&gt; You should store the wallet secret in GitHub's secret (&lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secrets&lt;/strong&gt;). On this project, I stored as &lt;code&gt;DEV_WALLET_SECRET&lt;/code&gt; and &lt;code&gt;PROD_WALLET_SECRET&lt;/code&gt; secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Fsecret.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-upgradable-smartcontract-to-blockchain%2Fraw%2Fmain%2Fdocs%2Fsecret.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Flows
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://github.com/actions/checkout" rel="noopener noreferrer"&gt;actions/checkout@v2&lt;/a&gt;&lt;/strong&gt; to checkout source code from the repository&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://github.com/actions/setup-node" rel="noopener noreferrer"&gt;actions/setup-node@v2&lt;/a&gt;&lt;/strong&gt; to setup nodejs&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://github.com/actions/cache" rel="noopener noreferrer"&gt;actions/cache@v2&lt;/a&gt;&lt;/strong&gt; to cache dependencies&lt;/li&gt;
&lt;li&gt;Install build dependencies (&lt;code&gt;yarn&lt;/code&gt;, &lt;code&gt;node-gyp&lt;/code&gt;, &lt;code&gt;node-gyp-build&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Install dependencies from &lt;code&gt;yarn.lock&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Synchronize previous built artifacts from the deployment branch&lt;/li&gt;
&lt;li&gt;Build and Migrate smartcontracts&lt;/li&gt;
&lt;li&gt;Push new built artifacts into the deployment branch&lt;/li&gt;
&lt;li&gt;Release smartcontracts' JSON (including ABI)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actionshackathon21</category>
      <category>blockchain</category>
      <category>ethereum</category>
      <category>smartcontract</category>
    </item>
    <item>
      <title>Automatic build and publish documentations on Github Pages</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Mon, 15 Nov 2021 18:33:23 +0000</pubDate>
      <link>https://dev.to/martinpham/automatic-build-and-publish-documentations-on-github-pages-2249</link>
      <guid>https://dev.to/martinpham/automatic-build-and-publish-documentations-on-github-pages-2249</guid>
      <description>&lt;h3&gt;
  
  
  Build documentations workflow
&lt;/h3&gt;

&lt;p&gt;With any project you work on, especially an open source project, &lt;strong&gt;documentation&lt;/strong&gt; should be one of the most important things to do. It helps the end-users understand how to use the application, it helps the new developers understand how to work with the project, ... &lt;/p&gt;

&lt;p&gt;With &lt;a href="https://www.mkdocs.org/"&gt;MkDocs&lt;/a&gt;, we can maintenance the documents easily. Just write documentations in Markdown, and it will build it into HTML static pages for you. Beside it, with the help from &lt;a href="https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions"&gt;GitHub workflows&lt;/a&gt;, we can also build the documentations automatically on every push into our GIT repository.&lt;/p&gt;

&lt;p&gt;I'd like to share with you my workflow to build documentations. It will listen for changes on documentation files (*.md), then build it and publish it into Github Pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J7nFAQyg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubefi8yguuhoi4qnlsby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J7nFAQyg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubefi8yguuhoi4qnlsby.png" alt="Result" width="880" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category: DIY Deployments
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ActionsHackathon21"&gt;
        ActionsHackathon21
      &lt;/a&gt; / &lt;a href="https://github.com/ActionsHackathon21/build-documentations"&gt;
        build-documentations
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Build documentations&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;This project follows the DEV.to &lt;a href="https://dev.to/devteam/join-us-for-the-2021-github-actions-hackathon-on-dev-4hn4" rel="nofollow"&gt;#ActionsHackathon21&lt;/a&gt; hackathon.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Use GitHub Actions and Workflows to build your documentations with MkDocs, then publish to Github Pages.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/build-documentations/raw/main/screenshot.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BXFgPxZ3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/ActionsHackathon21/build-documentations/raw/main/screenshot.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/build-documentations/raw/main/screenshot2.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IiKWZEit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/ActionsHackathon21/build-documentations/raw/main/screenshot2.png" alt="Result"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Check the complete workflow here (&lt;a href="https://github.com/ActionsHackathon21/build-documentations.github/workflows/build-documentations.yml"&gt;build-documentations.yml&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;
Actions used&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actions/checkout"&gt;actions/checkout@v2&lt;/a&gt;&lt;/strong&gt; To checkout the source code from the repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actions/cache"&gt;actions/cache@v2&lt;/a&gt;&lt;/strong&gt; To cache the dependencies, allow us to re use them for future builds&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Configurations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You can config the branch which holds the documentations with &lt;code&gt;DOCUMENTATIONS_BRANCH&lt;/code&gt; variable.&lt;/li&gt;
&lt;li&gt;You can also configure the branches which you want to run this workflow, with &lt;code&gt;branches&lt;/code&gt; key.&lt;/li&gt;
&lt;li&gt;To configure the Github pages, you can go to &lt;strong&gt;settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;pages&lt;/strong&gt; &amp;gt; &lt;strong&gt;Source&lt;/strong&gt; and choose the documentation branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/build-documentations/raw/main/screenshot3.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---CGxs32l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/ActionsHackathon21/build-documentations/raw/main/screenshot3.png" alt="Config"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Flows&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://github.com/actions/checkout"&gt;actions/checkout@v2&lt;/a&gt;&lt;/strong&gt; to checkout source code from the repository&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;&lt;a href="https://github.com/actions/cache"&gt;actions/cache@v2&lt;/a&gt;&lt;/strong&gt; to cache dependencies (&lt;code&gt;.pip&lt;/code&gt; directory)&lt;/li&gt;
&lt;li&gt;Install dependencies with &lt;code&gt;pip&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build documentations with Mkdocs&lt;/li&gt;
&lt;li&gt;Synchronize built files with deployment branch&lt;/li&gt;
&lt;li&gt;Push build into the deployment branch&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ActionsHackathon21/build-documentations"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;ul&gt;
&lt;li&gt;README: &lt;a href="https://github.com/ActionsHackathon21/build-documentations/blob/main/README.md"&gt;https://github.com/ActionsHackathon21/build-documentations/blob/main/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Workflow file: &lt;a href="https://github.com/ActionsHackathon21/build-documentations/blob/main/.github/workflows/build-documentations.yml"&gt;https://github.com/ActionsHackathon21/build-documentations/blob/main/.github/workflows/build-documentations.yml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: &lt;a href="https://github.com/ActionsHackathon21/build-documentations/blob/main/COPYING"&gt;https://github.com/ActionsHackathon21/build-documentations/blob/main/COPYING&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can config the branch which holds the documentations with &lt;code&gt;DOCUMENTATIONS_BRANCH&lt;/code&gt; variable. &lt;/li&gt;
&lt;li&gt;You can also configure the branches which you want to run this workflow, with &lt;code&gt;branches&lt;/code&gt; key.&lt;/li&gt;
&lt;li&gt;To configure the Github pages, you can go to &lt;strong&gt;settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;pages&lt;/strong&gt; &amp;gt; &lt;strong&gt;Source&lt;/strong&gt; and choose the documentation branch.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actionshackathon21</category>
      <category>github</category>
      <category>mkdocs</category>
      <category>python</category>
    </item>
    <item>
      <title>Automatic release new version, and notify your followers about it, using Github workflows</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Sun, 14 Nov 2021 11:06:22 +0000</pubDate>
      <link>https://dev.to/martinpham/automatic-release-new-version-and-notify-your-followers-about-it-using-github-workflows-3o6m</link>
      <guid>https://dev.to/martinpham/automatic-release-new-version-and-notify-your-followers-about-it-using-github-workflows-3o6m</guid>
      <description>&lt;h3&gt;
  
  
  Automatic release new version on tag workflow
&lt;/h3&gt;

&lt;p&gt;Managing an open source project really takes a lot of efforts. There are many repeated tasks which wastes your time everywhere: Testing, Building, Releasing, ... and sometimes, you'd also want to update your followers with the new released version.&lt;/p&gt;

&lt;p&gt;Fortunately, Github actions &amp;amp; workflows came to rescue! With tons of available actions, we can automate many repeated &amp;amp; boring tasks, and keep our time to focus on code.&lt;/p&gt;

&lt;p&gt;Today I'd like to share with you a GitHub worklow to build and release your application into a Github release, then notify your followers about this new release.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0mPCPub3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4ussvgjdduko0hjefgm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0mPCPub3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4ussvgjdduko0hjefgm.png" alt="Workflow result" width="880" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6f3cei6A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fp01lnxv08xav8jj42ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6f3cei6A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fp01lnxv08xav8jj42ho.png" alt="Telegram announcement" width="880" height="929"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this worklow, we automate all the testing, building steps and releasing steps, everytime you push a new release tag (example: v0.0.1, v0.0.2,...). Then send a new message about the newly released version to a Telegram channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category: DIY Deployments
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3JOwpme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ActionsHackathon21"&gt;
        ActionsHackathon21
      &lt;/a&gt; / &lt;a href="https://github.com/ActionsHackathon21/release-on-tag"&gt;
        release-on-tag
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Release a new version on very tag, then notify your followers on social networks
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Release new version on tag&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;Bonus: Also notify your followers on every update!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This project follows the DEV.to &lt;a href="https://dev.to/devteam/join-us-for-the-2021-github-actions-hackathon-on-dev-4hn4" rel="nofollow"&gt;#ActionsHackathon21&lt;/a&gt; hackathon.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Use GitHub Actions and Workflows to build and release your application on every release tag.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/release-on-tag/raw/main/screenshot.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sbDnO9Yn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/ActionsHackathon21/release-on-tag/raw/main/screenshot.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then send an announcement to your Telegram channel about the new release&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/release-on-tag/raw/main/screenshot2.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8BE_Qmrr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/ActionsHackathon21/release-on-tag/raw/main/screenshot2.png" alt="Telegram"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Check the complete workflow here (&lt;a href="https://github.com/ActionsHackathon21/release-on-tag.github/workflows/release-on-tag.yml"&gt;release-on-tag.yml&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;
Actions used&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actions/checkout"&gt;actions/checkout@v2&lt;/a&gt;&lt;/strong&gt; To checkout the source code from the repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actions/cache"&gt;actions/cache@v2&lt;/a&gt;&lt;/strong&gt; To cache the dependencies, allow us to re use them for future builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/marvinpinto/action-automatic-releases"&gt;marvinpinto/action-automatic-releases@latest&lt;/a&gt;&lt;/strong&gt; To release your build to Github Release page&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Also &lt;strong&gt;&lt;a href="https://github.com/actions/setup-node"&gt;actions/setup-node@v2&lt;/a&gt;&lt;/strong&gt; for setup nodejs, although it's not required)&lt;/p&gt;
&lt;h2&gt;
Configurations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You can config the release tag prefix, with &lt;code&gt;on.push.tags&lt;/code&gt; key.&lt;/li&gt;
&lt;li&gt;To send announcement to Telegram, you need to configure the workflow using following steps
&lt;ul&gt;
&lt;li&gt;Talk with Telegram's &lt;a href="https://t.me/BotFather" rel="nofollow"&gt;@BotFather&lt;/a&gt; to create a new bot if you don't have one. We will use this bot to send messages to the Telegram channel. He…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ActionsHackathon21/release-on-tag"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;README: &lt;a href="https://github.com/ActionsHackathon21/release-on-tag/blob/main/README.md"&gt;https://github.com/ActionsHackathon21/release-on-tag/blob/main/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Workflow file: &lt;a href="https://github.com/ActionsHackathon21/release-on-tag/blob/main/.github/workflows/release-on-tag.yml"&gt;https://github.com/ActionsHackathon21/release-on-tag/blob/main/.github/workflows/release-on-tag.yml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: &lt;a href="https://github.com/ActionsHackathon21/release-on-tag/blob/main/COPYING"&gt;https://github.com/ActionsHackathon21/release-on-tag/blob/main/COPYING&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the repository, there is a sample NextJS project, however you can change a bit on the workflow file to match your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You can config the release tag prefix, with &lt;code&gt;on.push.tags&lt;/code&gt; key.&lt;/li&gt;
&lt;li&gt;To send announcement to Telegram, you need to configure the workflow using following steps:

&lt;ul&gt;
&lt;li&gt;Talk with Telegram's &lt;a href="https://t.me/BotFather"&gt;@BotFather&lt;/a&gt; to create a new bot if you don't have one. We will use this bot to send messages to the Telegram channel. He will give you the &lt;strong&gt;token access the HTTP API&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On your Telegram channel, grant admin permissions to the bot.&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;TELEGRAM_CHANNEL&lt;/code&gt; variable.&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;TELEGRAM_BOT_TOKEN&lt;/code&gt; secret (using the token access above) into your repository secret (&lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secrets&lt;/strong&gt; &amp;gt; &lt;strong&gt;New repository secret&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-release
&lt;/h2&gt;

&lt;p&gt;There is also a pre-release workflow (&lt;a href="https://github.com/ActionsHackathon21/release-on-tag/blob/main/.github/workflows/prerelease-on-push.yml"&gt;prerelease-on-push.yml&lt;/a&gt;), which will build and create a pre-release version of your application, on every push to &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1EQNJp7W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bp6kb7z1zr6j88dluvye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1EQNJp7W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bp6kb7z1zr6j88dluvye.png" alt="Pre-release version" width="880" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>actionshackathon21</category>
      <category>github</category>
      <category>telegram</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Automatic deployment to GIT branch with GitHub workflow</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Tue, 09 Nov 2021 13:55:26 +0000</pubDate>
      <link>https://dev.to/martinpham/automatic-deployment-to-git-branch-with-github-workflow-2ai6</link>
      <guid>https://dev.to/martinpham/automatic-deployment-to-git-branch-with-github-workflow-2ai6</guid>
      <description>&lt;h3&gt;
  
  
  Deploy application to GIT branch workflow
&lt;/h3&gt;

&lt;p&gt;Sometimes, there are limitations which prevent your deployment flow from working properly. One of these limitations we were facing sometime, was the build and release process. &lt;/p&gt;

&lt;p&gt;Usually, we store our code on a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; repository, then setup a webhook on it. So each time we push a new commit into the release branch, GitHub will trigger a webhook call to a script our server, which pulls the latest commit, builds the application, and releases the build. &lt;/p&gt;

&lt;p&gt;It was a nice deployment flow, everything including pulling, testing, building, releasing,... worked automatically. However, after months, we've noticed a few issues with that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When our server receives a webhook call, it has to test and build the projects. As the project continues growing bigger, the build process becomes slower. Everytime it builds, our server's resources (CPU, RAM, ...) spikes up, affects performances of other running applications.&lt;/li&gt;
&lt;li&gt;It's hard to rollback to different deployments. &lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After discovering &lt;a href="https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions" rel="noopener noreferrer"&gt;GitHub workflows&lt;/a&gt;, we've found a way to improve our deployment process, with many &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;. And today we'd like to share it with you: &lt;strong&gt;We've created a GitHub worklow to build and deploy our application into a Git branch.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;With this worklow, we automate all the testing and building steps, then store the ready-to-run build into another branch. Our server just needs to pull the latest build from the deployment branch, and release it. And sometime, if we want to switch between versions, simply switch between commits. We can also trace the build files change back to the commit which made it.&lt;/p&gt;

&lt;p&gt;Also, thanks to the &lt;strong&gt;actions/cache@v2&lt;/strong&gt; action, we could also reduce the building time by caching the dependencies. It allows us to re-use them for future builds&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; You can also use it to deploy to Github Pages, by selecting the destination branch as the Github Pages's branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category: DIY Deployments
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ActionsHackathon21" rel="noopener noreferrer"&gt;
        ActionsHackathon21
      &lt;/a&gt; / &lt;a href="https://github.com/ActionsHackathon21/deploy-to-git-branch" rel="noopener noreferrer"&gt;
        deploy-to-git-branch
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Use GitHub Actions and Workflows to build and deploy your applications to a branch. So you can just pull this branch to deploy on the production server, without building.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Deploy application to GIT branch&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This project follows the DEV.to &lt;a href="https://dev.to/devteam/join-us-for-the-2021-github-actions-hackathon-on-dev-4hn4" rel="nofollow"&gt;#ActionsHackathon21&lt;/a&gt; hackathon.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Use GitHub Actions and Workflows to build and deploy your applications to a branch. So you can just pull this branch to deploy on the production server, without building. You can also use it to deploy to Github Pages, by selecting the destination branch as the Github Pages's branch.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/ActionsHackathon21/deploy-to-git-branch/raw/main/screenshot.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FActionsHackathon21%2Fdeploy-to-git-branch%2Fraw%2Fmain%2Fscreenshot.png" alt="Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Check the complete workflow here (&lt;a href="https://github.com/ActionsHackathon21/deploy-to-git-branch.github/workflows/build-and-deploy-to-branch.yml" rel="noopener noreferrer"&gt;build-and-deploy-to-branch.yml&lt;/a&gt;)&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Actions used&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actions/checkout" rel="noopener noreferrer"&gt;actions/checkout@v2&lt;/a&gt;&lt;/strong&gt; To checkout the source code from the repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actions/cache" rel="noopener noreferrer"&gt;actions/cache@v2&lt;/a&gt;&lt;/strong&gt; To cache the dependencies, allow us to re use them for future builds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Also &lt;strong&gt;&lt;a href="https://github.com/actions/setup-node" rel="noopener noreferrer"&gt;actions/setup-node@v2&lt;/a&gt;&lt;/strong&gt; for setup nodejs, although it's not required)&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Configurations&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;You can config the deployment branch postfix, with &lt;code&gt;DEPLOY_BRANCH_POSTFIX&lt;/code&gt; variable. So the code on "main" branch will be built and pushed into &lt;code&gt;main-&amp;lt;DEPLOY_BRANCH_POSTFIX&amp;gt;&lt;/code&gt; branch&lt;/li&gt;
&lt;li&gt;You can also configure the branches which you want to run this workflow, with &lt;code&gt;branches&lt;/code&gt; key.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Flows&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;In this repository, I use…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ActionsHackathon21/deploy-to-git-branch" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;README: &lt;a href="https://github.com/ActionsHackathon21/deploy-to-git-branch/blob/main/README.md" rel="noopener noreferrer"&gt;https://github.com/ActionsHackathon21/deploy-to-git-branch/blob/main/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Workflow file: &lt;a href="https://github.com/ActionsHackathon21/deploy-to-git-branch/blob/main/.github/workflows/build-and-deploy-to-branch.yml" rel="noopener noreferrer"&gt;https://github.com/ActionsHackathon21/deploy-to-git-branch/blob/main/.github/workflows/build-and-deploy-to-branch.yml&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: &lt;a href="https://github.com/ActionsHackathon21/deploy-to-git-branch/blob/main/COPYING" rel="noopener noreferrer"&gt;https://github.com/ActionsHackathon21/deploy-to-git-branch/blob/main/COPYING&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the repository, there is a sample NextJS project, however you can change a bit on the workflow file to match your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can config the deployment branch postfix, with the &lt;code&gt;DEPLOY_BRANCH_POSTFIX&lt;/code&gt; variable. Example: the code on &lt;code&gt;main&lt;/code&gt; branch will be built and pushed into &lt;code&gt;main-&amp;lt;DEPLOY_BRANCH_POSTFIX&amp;gt;&lt;/code&gt; branch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can also configure the branches which you want to run this workflow, with branches key.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actionshackathon21</category>
      <category>javascript</category>
      <category>docker</category>
      <category>github</category>
    </item>
    <item>
      <title>Coronavirus COVID-19 outbreak dashboard
</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Thu, 19 Mar 2020 11:26:06 +0000</pubDate>
      <link>https://dev.to/martinpham/coronavirus-covid-19-outbreak-dashboard-3j5l</link>
      <guid>https://dev.to/martinpham/coronavirus-covid-19-outbreak-dashboard-3j5l</guid>
      <description>&lt;p&gt;TLDR, I've built a Coronavirus COVID-19 dashboard &lt;a href="https://www.cov19.xyz/"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  COVID-19 outbreak is not a hoax anymore
&lt;/h2&gt;

&lt;p&gt;I've seen people around calling it "just-another-flu". And to be honest, I was thinking the same, until 3 weeks ago, when Italy started to see growing COVID-19 clusters. And 2 weeks ago, Italy went full nation lockdown. Our life changed completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lacking information
&lt;/h2&gt;

&lt;p&gt;We've seen what happened in Wuhan, China, in Korea. But we never thought it would happen here.&lt;br&gt;
We didn't know what to protect ourselves.&lt;br&gt;
We lost in fake news, in fake articles (I saw some articles on Facebook, saying hand dryers can kill the viruses!)&lt;/p&gt;

&lt;h2&gt;
  
  
  That's why I've decided to build a &lt;a href="https://www.cov19.xyz/"&gt;COVID-19 dashboard &lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I want to update the current situation to everyone, in every country. So people can see what's happening with other countries, and understand that it will happen in their country as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XxrzxyIY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mondbgqdsht7pvax7wqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XxrzxyIY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mondbgqdsht7pvax7wqh.png" alt="Alt Text" width="880" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vcPPrOVu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/enrvpav7krk6euszfe8x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vcPPrOVu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/enrvpav7krk6euszfe8x.png" alt="Alt Text" width="880" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--by5ctppa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jqv4q159c2mef8ne1ii7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--by5ctppa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jqv4q159c2mef8ne1ii7.png" alt="Alt Text" width="880" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I want to put trusted information from WHO and universities, experts. To help people protect themselves and their family.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--grcWehAW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9f23usckfbnla4gvdkvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--grcWehAW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9f23usckfbnla4gvdkvi.png" alt="Alt Text" width="880" height="613"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't forget, our country is fully locked down, in just 3 weeks. &lt;br&gt;
Thanks&lt;/p&gt;

</description>
      <category>coronavirus</category>
      <category>react</category>
      <category>kubernetes</category>
      <category>covid19</category>
    </item>
    <item>
      <title>Develop a Telegram chat bot to update Coronavirus COVID-19 situation</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Tue, 25 Feb 2020 10:22:15 +0000</pubDate>
      <link>https://dev.to/martinpham/i-ve-develop-a-telegram-chat-bot-to-update-coronavirus-covid-19-situation-2jlh</link>
      <guid>https://dev.to/martinpham/i-ve-develop-a-telegram-chat-bot-to-update-coronavirus-covid-19-situation-2jlh</guid>
      <description>&lt;h1&gt;
  
  
  TLDR
&lt;/h1&gt;

&lt;p&gt;Try the bot here &lt;a href="https://t.me/covid19liveupdate"&gt;https://t.me/covid19liveupdate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NUsp4m8j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/iadqpsl6xnis9gjihiqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NUsp4m8j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/iadqpsl6xnis9gjihiqx.png" alt="Alt Text" width="852" height="1696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Coronavirus COVID-19
&lt;/h1&gt;

&lt;p&gt;As all you've already known, the novel coronavirus COVID-19 is affecting 37 countries and territories around the world and 1 international conveyance (the "Diamond Princess" cruise ship harbored in Yokohama, Japan). None of us want to panic, but informations are very helpful for every people.&lt;/p&gt;

&lt;h1&gt;
  
  
  How I created the bot?
&lt;/h1&gt;

&lt;p&gt;With &lt;code&gt;NodeJS&lt;/code&gt; and &lt;code&gt;telegraf&lt;/code&gt; framework, it's pretty easy to create a Telegram bot for youself.&lt;/p&gt;

&lt;h1&gt;
  
  
  1) Create a bot
&lt;/h1&gt;

&lt;p&gt;Before doing everything, you need to register a new bot with Telegram. Just need to chat with &lt;a href="https://telegram.me/BotFather"&gt;@BotFather&lt;/a&gt;, he will help you through steps (start with &lt;code&gt;/newbot&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;In the end, the &lt;code&gt;@BotFather&lt;/code&gt; will register your bot, and give you an &lt;code&gt;API Token&lt;/code&gt;. Write it down, we'll need it for next steps.&lt;/p&gt;

&lt;h1&gt;
  
  
  2) Write bot's logic
&lt;/h1&gt;

&lt;p&gt;It's the heart of your new bot, it will listen for messages, commands from users, and replies them as you prepared.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;telegraf&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;yarn add telegraf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Init the bot
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# bot.js

const Telegraf = require('telegraf')

const BOT_TOKEN = '*** The API Token you got from previous step ***'

const bot = new Telegraf(BOT_TOKEN)
bot.start((context) =&amp;gt; context.reply('Welcome!'))
bot.help((context) =&amp;gt; context.reply('You can say hi'))
bot.hears('hi', (context) =&amp;gt; context.reply('Hey there!'))
bot.launch()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;There it is! Very simple right? Your bot will say &lt;code&gt;"Welcome!"&lt;/code&gt; for new user, or says &lt;code&gt;"You can hi"&lt;/code&gt; if user asks for help, and replies &lt;code&gt;"Hey there!"&lt;/code&gt; if user says &lt;code&gt;"hi"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How my bot can send message?
&lt;/h1&gt;

&lt;p&gt;There are 2 cases which the bot can send a message:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send message / Reply to a chat (bot &amp;lt;-&amp;gt; user)&lt;/li&gt;
&lt;li&gt;Broadcast to a channel (admin -&amp;gt; users)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  1) Send messages in a chat
&lt;/h1&gt;

&lt;p&gt;To send a message in a chat, you need to know the current chat. You can take the chat's information in the &lt;code&gt;context&lt;/code&gt; variable in the previous example.&lt;/p&gt;

&lt;p&gt;When you're able to take the Chat ID from &lt;code&gt;context.chat&lt;/code&gt;, you're ready to send a message:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reply to current chat:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context.reply('*** Your message ***')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Send a message to current chat (without user's message):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bot.telegram.sendMessage('*** Chat ID ***', '*** Your message ***')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  2) Broadcast messages to a channel
&lt;/h1&gt;

&lt;p&gt;To boardcast a message in a chat, you need to add the bot into your channel and make it becomes an admin. &lt;/p&gt;

&lt;p&gt;Then you can simply send messages using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bot.telegram.sendMessage('@YourChannelName', '*** Your message ***')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;That's all! In next tutorials, we'll work together to deploy our bots to cloud, and keep it running to handle user's requests.&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>bot</category>
      <category>scrape</category>
      <category>coronavirus</category>
    </item>
    <item>
      <title>Try PHP 8.0 beta features with Docker / Kubernetes</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Mon, 03 Feb 2020 10:47:13 +0000</pubDate>
      <link>https://dev.to/martinpham/try-php-8-0-beta-features-with-docker-kubernetes-5f61</link>
      <guid>https://dev.to/martinpham/try-php-8-0-beta-features-with-docker-kubernetes-5f61</guid>
      <description>&lt;p&gt;PHP 8.0's development had started one year ago. With many promising features like: Union types, JIT, Static return type, Weak maps, ..., probably you'd like to give it a try. Or simply, you just want to know if your codebase will run fines with future PHP version. So I've created PHP 8.0 beta Docker images for you :) &lt;/p&gt;

&lt;p&gt;The base image &lt;code&gt;martinpham/php8:fpm-alpine&lt;/code&gt; contains PHP 8.0 FPM, built from PHP source code, and based on Alpine Linux, so it's quite small and fast. It also supports multiple architectures (linux/amd64, linux/arm64, linux/386, linux/arm/v7, linux/arm/v6), so you can run it even on a small Raspberry Pi!&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker
&lt;/h1&gt;

&lt;p&gt;You can try to run it now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 9000:9000 &lt;span class="nt"&gt;--rm&lt;/span&gt; martinpham/php8:fpm-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker will pull the image, and creates a new container with this image, also exposes port 9000 for PHP-FPM server. (Note: The &lt;code&gt;--rm&lt;/code&gt; option tells Docker to remove the container after it finishes).&lt;/p&gt;

&lt;p&gt;If everything goes fine, you'd see logs from Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;NOTICE: fpm is running, pid 1
NOTICE: ready to handle connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make sure, you could also try to send request to the PHP-FPM server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;REQUEST_METHOD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;GET cgi-fcgi &lt;span class="nt"&gt;-bind&lt;/span&gt; &lt;span class="nt"&gt;-connect&lt;/span&gt; localhost:9000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Replace localhost with your Docker's Host IP)&lt;/p&gt;

&lt;p&gt;PHP-FPM will respond:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-Powered-By: PHP/8.0.0-dev
Content-type: text/html; charset=UTF-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is good! You are running your first PHP 8.0 beta - FPM server!&lt;/p&gt;

&lt;h1&gt;
  
  
  Kubernetes
&lt;/h1&gt;

&lt;p&gt;If you were following &lt;a href="https://www.martinpham.com/2019/12/07/having-fun-with-kubernetes-1/"&gt;my Kuberetes tutorial&lt;/a&gt; before, probably you'd like to deploy it on your Kubernetes cluster :). &lt;br&gt;
Well it's very easy, here is a sample configuration, with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A persistent volume (&lt;code&gt;code-pvc&lt;/code&gt;) for storing application code&lt;/li&gt;
&lt;li&gt;A config map (&lt;code&gt;nginx-config&lt;/code&gt;) for nginx configuration&lt;/li&gt;
&lt;li&gt;A deployment (&lt;code&gt;app-deployment&lt;/code&gt;) with PHP 8 FPM server and NGINX web server&lt;/li&gt;
&lt;li&gt;A service (&lt;code&gt;app-service&lt;/code&gt;) to expose deployed pod (tagged with &lt;code&gt;name: app-pod&lt;/code&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;## App code volume&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-pvc&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10Gi&lt;/span&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;## Nginx config&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx.conf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;events {&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;http {&lt;/span&gt;
      &lt;span class="s"&gt;server {&lt;/span&gt;
        &lt;span class="s"&gt;listen 80;&lt;/span&gt;
        &lt;span class="s"&gt;listen [::]:80;&lt;/span&gt;

        &lt;span class="s"&gt;root /var/www/html;&lt;/span&gt;
        &lt;span class="s"&gt;index index.html index.htm index.php;&lt;/span&gt;

        &lt;span class="s"&gt;location / {&lt;/span&gt;
          &lt;span class="s"&gt;try_files $uri $uri/ =404;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;

        &lt;span class="s"&gt;location ~ \.php$ {&lt;/span&gt;
          &lt;span class="s"&gt;include fastcgi_params;&lt;/span&gt;
          &lt;span class="s"&gt;fastcgi_param REQUEST_METHOD $request_method;&lt;/span&gt;
          &lt;span class="s"&gt;fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;&lt;/span&gt;
          &lt;span class="s"&gt;fastcgi_pass 127.0.0.1:9000;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;

&lt;span class="s"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;## App deployment&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-deployment&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recreate&lt;/span&gt;    
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-pod&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-pod&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-pod&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-files-volume&lt;/span&gt;
          &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-pvc&lt;/span&gt;

        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config-volume&lt;/span&gt;
          &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config&lt;/span&gt;

      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

        &lt;span class="c1"&gt;# php-fpm&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;phpfpm&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;martinpham/php8:fpm-alpine&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-files-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100m&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;50m&lt;/span&gt;

        &lt;span class="c1"&gt;# nginx&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-files-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/nginx.conf&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx.conf&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100m&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;50m&lt;/span&gt;

          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
            &lt;span class="na"&gt;successThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="s"&gt;--------&lt;/span&gt;

&lt;span class="c1"&gt;## App service&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-pod&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, I've also built an "extra" image, called &lt;code&gt;martinpham/php8:fpm-extra-alpine&lt;/code&gt;, which extends from the base image, with mysqli, gd, pdo_mysql and opcache extensions, so you can start to try other extensions with PHP 8.0.&lt;/p&gt;




&lt;p&gt;PS: All images above are open-sourced, you can find it here &lt;a href="https://github.com/MartinPham/php8-docker"&gt;https://github.com/MartinPham/php8-docker&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>beta</category>
      <category>docker</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Symfony 5 development with Docker</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Mon, 30 Dec 2019 09:03:06 +0000</pubDate>
      <link>https://dev.to/martinpham/symfony-5-development-with-docker-4hj8</link>
      <guid>https://dev.to/martinpham/symfony-5-development-with-docker-4hj8</guid>
      <description>&lt;p&gt;We were &lt;a href="https://www.martinpham.com/2019/12/07/having-fun-with-kubernetes-1/" rel="noopener noreferrer"&gt;playing with Kubernetes&lt;/a&gt; last week, however the project was just a small PHP file with phpinfo() function call, no big deal.&lt;/p&gt;

&lt;p&gt;Today my colleague asked me to guide him a bit on Docker, because he’d like to try it with a real world example: Developing a &lt;a href="https://symfony.com/" rel="noopener noreferrer"&gt;Symfony&lt;/a&gt; project. So let’s take a look at this, it's quick, easy and fun!&lt;/p&gt;

&lt;p&gt;Symfony uses &lt;a href="https://getcomposer.org/" rel="noopener noreferrer"&gt;Composer&lt;/a&gt; to manage its dependencies and scripts, namespaces,.. with a file named composer.json. Dependencies will be download to a directory called &lt;strong&gt;vendor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Focus on development, we'd like to create a ready configured &amp;amp; isolated environment, so anyone can clone the repository and run the application easily. So we’re gonna use 3 containers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MySQL, with mounted volume for data persistent&lt;/li&gt;
&lt;li&gt;PHP-FPM, with mounted volume for application’s code&lt;/li&gt;
&lt;li&gt;NGINX, with mounted volumes for configurations, logs, and share mounted volume with PHP-FPM for application’s assets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fai2xx9dproym7xqg8ggs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fai2xx9dproym7xqg8ggs.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will also need to use some environment variables for containers’ parameters, like database credentials, application secret key,…&lt;/p&gt;

&lt;p&gt;We’re gonna use &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker-Compose&lt;/a&gt; to put configurations and run all containers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project
│
├── docker-compose.yml
│
├── database/
│   ├── Dockerfile
│   └── data/
│
└── php-fpm/
│   └── Dockerfile
│
├── nginx/
│   ├── Dockerfile
│   └── nginx.conf
│
└── logs/
    └── nginx/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  MySQL Database
&lt;/h1&gt;

&lt;p&gt;Let’s just create a MariaDB container&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# docker/database/Dockerfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mariadb:latest&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["mysqld"]&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3306&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explaination&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use &lt;a href="https://hub.docker.com/_/mariadb" rel="noopener noreferrer"&gt;MariaDB official image&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run mysqld to start the server&lt;/li&gt;
&lt;li&gt;Expose port 3306 for database connection&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  PHP-FPM
&lt;/h1&gt;

&lt;p&gt;With PHP-FPM container, we’d like to install dependencies and run database migrations at start. So we need to install the PDO MySQL extension, then composer, and then Symfony migration script.&lt;/p&gt;

&lt;p&gt;However, it could be a problem if we run the migration before the MySQL server is ready. We will need to handle this also.&lt;/p&gt;

&lt;p&gt;With Docker-compose, we can specify a depends_on configuration to tell it wait for another container. But it doesn’t mean Docker-compose will wait until the MySQL server is ready, it only waits until the MySQL container is up. &lt;/p&gt;

&lt;p&gt;Fortunately, with the help from &lt;a href="https://github.com/vishnubob/wait-for-it" rel="noopener noreferrer"&gt;wait-for-it&lt;/a&gt; script, we can try to wait until the MySQL container’s port 3306 is Open (Or you can even try to wait until you can connect to the MySQL using credentials).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# docker/php-fpm/Dockerfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:fpm-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; wait-for-it.sh /usr/bin/wait-for-it&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/bin/wait-for-it
&lt;span class="k"&gt;RUN &lt;/span&gt;apk &lt;span class="nt"&gt;--update&lt;/span&gt; &lt;span class="nt"&gt;--no-cache&lt;/span&gt; add git
&lt;span class="k"&gt;RUN &lt;/span&gt;docker-php-ext-install pdo_mysql
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=composer /usr/bin/composer /usr/bin/composer&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; composer install ; wait-for-it database:3306 -- bin/console doctrine:migrations:migrate ;  php-fpm &lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 9000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explaination&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use PHP-FPM offical image&lt;/li&gt;
&lt;li&gt;Copy wait-for-it script into the container&lt;/li&gt;
&lt;li&gt;Allow execution for wait-for-it&lt;/li&gt;
&lt;li&gt;Add git for dependencies installation&lt;/li&gt;
&lt;li&gt;Install PHP PDO MySQL&lt;/li&gt;
&lt;li&gt;Take composer file from Composer official image&lt;/li&gt;
&lt;li&gt;Set working dir to /var/www&lt;/li&gt;
&lt;li&gt;Install dependencies, then wait until the MySQL container is Online to run migration script. Finally, run php-fpm to start the server&lt;/li&gt;
&lt;li&gt;Expose PHP-FPM port (9000)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  NGINX
&lt;/h1&gt;

&lt;p&gt;Okey, this part is a bit complex, we’re gonna create the NGINX configuration file, the PHP-FPM proxy, and a separated file for default NGINX site.&lt;/p&gt;

&lt;p&gt;First the Dockerfile definition&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# docker/nginx/Dockerfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /var/www&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx"]&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Explaination&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As above, we use NGINX official image&lt;/li&gt;
&lt;li&gt;Set working dir to /var/www, the same directory with PHP-FPM since we’re gonna share this with a mounted volume&lt;/li&gt;
&lt;li&gt;Start nginx&lt;/li&gt;
&lt;li&gt;Expose the port 80 for web&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, an NGINX server configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# docker/nginx/nginx.conf
&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;  &lt;span class="n"&gt;nginx&lt;/span&gt;;
&lt;span class="n"&gt;worker_processes&lt;/span&gt;  &lt;span class="m"&gt;4&lt;/span&gt;;
&lt;span class="n"&gt;daemon&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;

&lt;span class="n"&gt;error_log&lt;/span&gt;  /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;error&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;warn&lt;/span&gt;;
&lt;span class="n"&gt;pid&lt;/span&gt;        /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;run&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;.&lt;span class="n"&gt;pid&lt;/span&gt;;

&lt;span class="n"&gt;events&lt;/span&gt; {
    &lt;span class="n"&gt;worker_connections&lt;/span&gt;  &lt;span class="m"&gt;1024&lt;/span&gt;;
}


&lt;span class="n"&gt;http&lt;/span&gt; {
    &lt;span class="n"&gt;include&lt;/span&gt;       /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;mime&lt;/span&gt;.&lt;span class="n"&gt;types&lt;/span&gt;;
    &lt;span class="n"&gt;default_type&lt;/span&gt;  &lt;span class="n"&gt;application&lt;/span&gt;/&lt;span class="n"&gt;octet&lt;/span&gt;-&lt;span class="n"&gt;stream&lt;/span&gt;;
    &lt;span class="n"&gt;access_log&lt;/span&gt;  /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;access&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;;
    &lt;span class="n"&gt;sendfile&lt;/span&gt;        &lt;span class="n"&gt;on&lt;/span&gt;;
    &lt;span class="n"&gt;keepalive_timeout&lt;/span&gt;  &lt;span class="m"&gt;65&lt;/span&gt;;

    &lt;span class="n"&gt;include&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;conf&lt;/span&gt;.&lt;span class="n"&gt;d&lt;/span&gt;/*.&lt;span class="n"&gt;conf&lt;/span&gt;;
    &lt;span class="n"&gt;include&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;sites&lt;/span&gt;-&lt;span class="n"&gt;available&lt;/span&gt;/*.&lt;span class="n"&gt;conf&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An NGINX – PHP-FPM configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# docker/nginx/conf.d/default.conf
&lt;/span&gt;
&lt;span class="n"&gt;upstream&lt;/span&gt; &lt;span class="n"&gt;php&lt;/span&gt;-&lt;span class="n"&gt;upstream&lt;/span&gt; {
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;php&lt;/span&gt;-&lt;span class="n"&gt;fpm&lt;/span&gt;:&lt;span class="m"&gt;9000&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an NGINX site’s configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# docker/nginx/sites/default.conf
&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="n"&gt;default_server&lt;/span&gt;;
    &lt;span class="n"&gt;listen&lt;/span&gt; [::]:&lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="n"&gt;default_server&lt;/span&gt; &lt;span class="n"&gt;ipv6only&lt;/span&gt;=&lt;span class="n"&gt;on&lt;/span&gt;;

    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt;;
    &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;www&lt;/span&gt;/&lt;span class="n"&gt;public&lt;/span&gt;;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;htm&lt;/span&gt;;

    &lt;span class="n"&gt;location&lt;/span&gt; / {
         &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt;/ /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;$&lt;span class="n"&gt;is_args&lt;/span&gt;$&lt;span class="n"&gt;args&lt;/span&gt;;
    }

    &lt;span class="n"&gt;location&lt;/span&gt; ~ \.&lt;span class="n"&gt;php&lt;/span&gt;$ {
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt; =&lt;span class="m"&gt;404&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="n"&gt;php&lt;/span&gt;-&lt;span class="n"&gt;upstream&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_index&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_buffers&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_buffer_size&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_param&lt;/span&gt; &lt;span class="n"&gt;SCRIPT_FILENAME&lt;/span&gt; $&lt;span class="n"&gt;document_root&lt;/span&gt;$&lt;span class="n"&gt;fastcgi_script_name&lt;/span&gt;;
        &lt;span class="n"&gt;fastcgi_read_timeout&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;;
        &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="n"&gt;fastcgi_params&lt;/span&gt;;
    }

    &lt;span class="n"&gt;location&lt;/span&gt; ~ /\.&lt;span class="n"&gt;ht&lt;/span&gt; {
        &lt;span class="n"&gt;deny&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Docker-Compose configuration
&lt;/h1&gt;

&lt;p&gt;We have 3 container definitions, now we just need to setup a Docker-compose configuration to connect all togethers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker/docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./database&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_DATABASE=${DATABASE_NAME}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_USER=${DATABASE_USER}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_PASSWORD=${DATABASE_PASSWORD}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MYSQL_ROOT_PASSWORD=${DATABASE_ROOT_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3306:3306"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./database/init.sql:/docker-entrypoint-initdb.d/init.sql&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./database/data:/var/lib/mysql&lt;/span&gt;

  &lt;span class="na"&gt;php-fpm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./php-fpm&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;database&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;APP_ENV=${APP_ENV}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;APP_SECRET=${APP_SECRET}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@database:3306/${DATABASE_NAME}?serverVersion=5.7&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../src:/var/www&lt;/span&gt;

  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./nginx&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;../src:/var/www&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/nginx.conf:/etc/nginx/nginx.conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/sites/:/etc/nginx/sites-available&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/conf.d/:/etc/nginx/conf.d&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./logs:/var/log&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;php-fpm&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a sample environment variables:&lt;br&gt;
&lt;/p&gt;

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

DATABASE_NAME=symfony
DATABASE_USER=appuser
DATABASE_PASSWORD=apppassword
DATABASE_ROOT_PASSWORD=secret

APP_ENV=dev
APP_SECRET=24e17c47430bd2044a61c131c1cf6990
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Symfony
&lt;/h1&gt;

&lt;p&gt;Let’s proceed to the Symfony installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;symfony new src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Play time
&lt;/h1&gt;

&lt;p&gt;Everything is setup correctly! Let’s play with our containers!&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;When those containers are ready, you can start to open &lt;a href="http://localhost" rel="noopener noreferrer"&gt;http://localhost&lt;/a&gt;, you will see a Symfony 5 welcome screen. Everything works perfectly, have fun!&lt;/p&gt;

&lt;p&gt;I’ve create a repository for all the files we talked above &lt;a href="https://gitlab.com/martinpham/symfony-5-docker" rel="noopener noreferrer"&gt;https://gitlab.com/martinpham/symfony-5-docker&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>symfony</category>
      <category>docker</category>
      <category>container</category>
    </item>
    <item>
      <title>Secure your Kubernetes application with HTTPS</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Sat, 28 Dec 2019 00:13:02 +0000</pubDate>
      <link>https://dev.to/martinpham/secure-your-kubernetes-application-with-https-ng7</link>
      <guid>https://dev.to/martinpham/secure-your-kubernetes-application-with-https-ng7</guid>
      <description>&lt;p&gt;Hope you were having fun with &lt;a href="https://www.martinpham.com/2019/12/07/having-fun-with-kubernetes-1/" rel="noopener noreferrer"&gt;my Kubernetes tutorial&lt;/a&gt;! &lt;/p&gt;

&lt;p&gt;Today, I've got &lt;a href="https://dev.to/thorstenhirsch/comment/jcn2"&gt;a new comment from Thorsten Hirsch&lt;/a&gt;, asking about TLS certificate. Well,  &lt;a class="mentioned-user" href="https://dev.to/thorstenhirsch"&gt;@thorstenhirsch&lt;/a&gt;, I'm interested as with this thing as well :), and it was the first thing I had to check while I was learning Kubernetes. So I'm gonna share with you guys how did I solve this problem.&lt;/p&gt;

&lt;h1&gt;
  
  
  Requirement
&lt;/h1&gt;

&lt;p&gt;We'd like to add a TLS certificate to our application, which we built &lt;a href="https://www.martinpham.com/2019/12/14/having-fun-with-kubernetes-final/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Felbmq2f23mih7q3dbdpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Felbmq2f23mih7q3dbdpa.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Problem
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;What? problem? there is no problem! we were using &lt;code&gt;certbot&lt;/code&gt; thousands times before. Just install it, run a magical script to get a free &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let's Encrypt&lt;/a&gt;, then config it with nginx.&lt;/em&gt;&lt;br&gt;
If you are thinking like this, wait a second and think about this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your application lives in read-only containers, managed by Pods. And Pods are added/removed dynamically, by your configurations. And you will need to give nginx certificates when you start it.&lt;/li&gt;
&lt;li&gt;You can generate certificates, then pack it within the image, and deloy it. It's a &lt;strong&gt;terrible solution&lt;/strong&gt;. Because you'd need to deploy every time the certificates are gonna expired.&lt;/li&gt;
&lt;li&gt;You can store it in a shared volume, and config nginx to take certificates from it. It's another &lt;strong&gt;terrible solution&lt;/strong&gt;. Because you'd still need to manage the certificates yourself (even when you want to run it via a cron?)&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Solution
&lt;/h1&gt;

&lt;p&gt;Let's try to think about another solution (&lt;em&gt;Spoiler&lt;/em&gt;: It's &lt;strong&gt;awesome&lt;/strong&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We will keep our infrastructure as before, with minimum modifications. No changes to application, no changes to nginx, no changes to deployment.&lt;/li&gt;
&lt;li&gt;TLS certificate will be monitored and renewed automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First of all, I'd like to introduce you a new guy - &lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" rel="noopener noreferrer"&gt;Ingress&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9ra8tz1hfs7ohccallwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F9ra8tz1hfs7ohccallwv.png" alt="Ingress"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basically, Ingress is like a Router, which takes the incoming traffic then passes it to the corresponding Service, with the help from &lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/" rel="noopener noreferrer"&gt;Ingress Controllers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Simple example: You have 3 services: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apple-service&lt;/code&gt; (which runs &lt;a href="//apple.com"&gt;apple.com&lt;/a&gt; website, selling fruits)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pineapple-service&lt;/code&gt; (another website - pineapple.com, selling phone, tablet, computer,...)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pineapple-cloud-service&lt;/code&gt; (offering some cloud services for Pineapple's clients)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you want is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apple.com will be pointed to &lt;code&gt;apple-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;pineapple.com will be pointed to &lt;code&gt;pineapple-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;pineapple.com/cloud will be pointed to &lt;code&gt;pineapple-cloud-service&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ingress comes to help you here: Just need to create an Ingress object, with routing rules you want, then point those domains into the Ingress IP. And everything will be handled correctly!&lt;/p&gt;

&lt;h1&gt;
  
  
  Preparing stuffs
&lt;/h1&gt;

&lt;p&gt;Before everything, we need to prepare stuffs for our Ingress configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Install ingress-nginx&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/kubernetes/ingress-nginx" rel="noopener noreferrer"&gt;ingress-nginx&lt;/a&gt; is a Ingress Controller, which helps Ingress to route the traffic easily.&lt;/p&gt;

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

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml


&lt;/code&gt;&lt;/pre&gt;

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

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud-generic.yaml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For this Lab, we'd like to expose the Ingress with the Kube master's IP (&lt;code&gt;192.168.1.33&lt;/code&gt; as we configured before), so let's edit this controller a bit:&lt;/p&gt;

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

kubectl edit svc/ingress-nginx -n ingress-nginx


&lt;/code&gt;&lt;/pre&gt;

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

spec:
  externalIPs:
  - 192.168.1.33


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Install cert-manager&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://cert-manager.io/docs/" rel="noopener noreferrer"&gt;cert-manager&lt;/a&gt; is a Kubernetes controller, which helps you to manage certificates without pain. We're gonna use it to request &amp;amp; renew Let's encrypt TLS certificate for our application.&lt;/p&gt;

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

kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.12.0/cert-manager.yaml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Check all the pods are running correctly&lt;/p&gt;

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

kubectl get pods --namespace cert-manager


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Setup Ingress &amp;amp; TLS
&lt;/h1&gt;

&lt;p&gt;Back to our infrastructure: Before we were using a LoadBalancer as the start point, now we can just change it to stay behind in the cluster, and add an Ingress as the start point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

(Users) ---&amp;gt; Service ---&amp;gt; Pods


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Now&lt;/strong&gt;&lt;/p&gt;

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

(Users) ---&amp;gt; Ingress ---&amp;gt; Service ---&amp;gt; Pods


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We're gonna edit a bit the service file &lt;code&gt;service-loadbalancer.yml&lt;/code&gt; (We've created it &lt;a href="https://www.martinpham.com/2019/12/14/having-fun-with-kubernetes-final/" rel="noopener noreferrer"&gt;before&lt;/a&gt;)&lt;/p&gt;

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

apiVersion: v1
kind: Service
metadata:
  name: service-loadbalancer
spec:
  selector:
    name: templated-pod

  type: ClusterIP
  ports:  
  - name: http
    nodePort: null
    port: 80
    targetPort: 80
    protocol: TCP

  # type: LoadBalancer
  # ports:
  #   - port: 80
  #     targetPort: 80
  # externalIPs:
  #   - 192.168.1.33


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Apply it&lt;/p&gt;

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

kubectl apply -f service-loadbalancer.yml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Create a ClusterIssuer&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Create new file &lt;code&gt;cert_issuer.yml&lt;/code&gt;&lt;/p&gt;

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

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-site
  namespace: cert-manager
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: YOUR-EMAIL@HERE.TLD
    privateKeySecretRef:
      name: letsencrypt-prod-site
    solvers:
    - http01:
        ingress:
          class: nginx



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Apply it&lt;/p&gt;

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

kubectl apply -f cert_issuer.yml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Create an Ingress&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Here comes our Rockstar tonight: &lt;code&gt;ingress.yml&lt;/code&gt;&lt;/p&gt;

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

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod-site"

spec:
  tls:
  - hosts:
    - YOUR-DOMAIN-HERE.TLD
    secretName: site-tls

  rules:
  - host: YOUR-DOMAIN-HERE.TLD
    http:
      paths:
      - path: /
        backend:
          serviceName: service-loadbalancer
          servicePort: 80


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Nothing special:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line 12: Define your hostname which will run under this Ingress&lt;/li&gt;
&lt;li&gt;Line 16-22: Define a rule, to tell this Ingress: When user browses &lt;code&gt;YOUR-DOMAIN-HERE.TLD&lt;/code&gt;, under path &lt;code&gt;/&lt;/code&gt;, you'd like to pass him to the service &lt;code&gt;service-loadbalancer&lt;/code&gt; on port &lt;code&gt;80&lt;/code&gt; (defined in &lt;code&gt;service-loadbalancer.yml&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Remember with this Lab, you must point YOUR-DOMAIN-HERE.TLD to your IP address, then forward all traffic on port &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt; to the kube-master IP &lt;code&gt;192.168.1.33&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Show time:&lt;/p&gt;

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

kubectl apply -f ingress.yml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;*Note: requesting/renewing a new certificate from Let's encrypt could take some minutes. You can monitor here: *&lt;/p&gt;

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

kubectl describe certificate site-tls


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now you can try to browser &lt;a href="https://YOUR-DOMAIN-HERE.TLD/" rel="noopener noreferrer"&gt;https://YOUR-DOMAIN-HERE.TLD/&lt;/a&gt; on your favorite web browser, and open a &lt;em&gt;prosecco&lt;/em&gt; :)&lt;/p&gt;




&lt;p&gt;I've updated the repository &lt;a href="https://gitlab.com/martinpham/kubernetes-fun" rel="noopener noreferrer"&gt;https://gitlab.com/martinpham/kubernetes-fun&lt;/a&gt;, so you can take all the files we were talking about.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>ssl</category>
      <category>https</category>
      <category>nginx</category>
    </item>
    <item>
      <title>Having fun with Kubernetes - Final Chapter: Play time</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Mon, 23 Dec 2019 17:45:42 +0000</pubDate>
      <link>https://dev.to/martinpham/having-fun-with-kubernetes-final-chapter-play-time-4kka</link>
      <guid>https://dev.to/martinpham/having-fun-with-kubernetes-final-chapter-play-time-4kka</guid>
      <description>&lt;p&gt;Finally, you have &lt;a href="https://www.martinpham.com/2019/12/08/having-fun-with-kubernetes-4/"&gt;your very first k8s cluster&lt;/a&gt; with &lt;a href="https://www.martinpham.com/2019/12/08/having-fun-with-kubernetes-5/"&gt;2 nodes up and running&lt;/a&gt; well. Now let’s play with it!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(Optional) Install Kubernetes Dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes Dashboard is a very cool web-based UI for managing your k8s cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To access this dashboard, you might need to use proxy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you can access it by opening&lt;br&gt;
&lt;a href="http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/overview?namespace=default"&gt;http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/overview?namespace=default&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To login, you might need to create an user. However, for this Lab, we can just grab the namespace-controller token to login&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl -n kube-system describe secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will find the namespace-controller token from the output. Copy &amp;amp; Paste it into the dashboard authentication screen, and you will be authenticated.&lt;/p&gt;




&lt;h1&gt;
  
  
  Putting our Lego blocks
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Build and push application image into Docker registry&lt;/strong&gt;&lt;br&gt;
Go back to our repository, now we’d like to build it again, with version tag. Then push it into our Docker registry. So after it, Kubernetes can pull the image and deploy it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker build . -t martinpham/kphp:v1
$ docker tag martinpham/kphp:v1 192.168.1.33:5000/martinpham/kphp:v1
$ docker push 192.168.1.33:5000/martinpham/kphp:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are building our Docker image, with name &lt;code&gt;martinpham/kphp&lt;/code&gt; tag &lt;code&gt;v1&lt;/code&gt; (You can use whatever name you want, don’t worry!). Then push it under name &lt;code&gt;192.168.1.33:5000/martinpham/kphp:v1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy pods&lt;/strong&gt;&lt;br&gt;
Create a Configmap configuration file: &lt;code&gt;config.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-config
data:
  nginx.conf: |
    events {
    }
    http {
      server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;
        index index.html index.htm index.php;
        server_name _;
        location / {
          try_files $uri $uri/ =404;
        }
        location ~ \.php$ {
          include fastcgi_params;
          fastcgi_param REQUEST_METHOD $request_method;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          fastcgi_pass 127.0.0.1:9000;
        }
      }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing to scare!, we’re making a shared config across our k8s cluster. You can apply it now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f config.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a Deployment configuration file: &lt;code&gt;deployment.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment
  labels:
    name: deployment
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 2
  selector:
    matchLabels:
      name: templated-pod
  template:
    metadata:
      name: deployment-template
      labels:
        name: templated-pod
    spec:
      volumes:
        - name: app-files
          emptyDir: {}

        - name: nginx-config-volume
          configMap:
            name: nginx-config

      containers:
        - image: 192.168.1.33:5000/martinpham/kphp:v1
          name: app
          volumeMounts:
            - name: app-files
              mountPath: /var/www/html
          lifecycle:
            postStart:
              exec:
                command: ["/bin/sh", "-c", "cp -r /app/. /var/www/html"]
          resources:
            limits:
              cpu: 100m
            requests:
              cpu: 50m


        - image: nginx:latest
          name: nginx
          volumeMounts:
            - name: app-files
              mountPath: /var/www/html
            - name: nginx-config-volume
              mountPath: /etc/nginx/nginx.conf
              subPath: nginx.conf
          resources:
            limits:
              cpu: 100m
            requests:
              cpu: 50m

          ports:
          - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 3
            periodSeconds: 3
            successThreshold: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, it’s a bit spaghetti-style, but again, don’t worry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line 6: We tag this deployment as name = deployment&lt;/li&gt;
&lt;li&gt;Line 8: We tell k8s to have 3 replicas during deployment.&lt;/li&gt;
&lt;li&gt;Line 13: During deployment, k8s must keep at least 1 Pod available, so our website will be still online, ensure zero-downtime during deployment.&lt;/li&gt;
&lt;li&gt;Line 21: We’re tagging our future Pod by name = templated-pod, so k8s can find them&lt;/li&gt;
&lt;li&gt;Line 24: We create a shared volume between nginx and php, which contains our application runtime&lt;/li&gt;
&lt;li&gt;Line 27: We map the config created above into another volume&lt;/li&gt;
&lt;li&gt;Line 32: We create a php-fpm container from our created image, and mount the shared volume into /var/www/html&lt;/li&gt;
&lt;li&gt;Line 40: We copy all application runtime from the image into the shared volume, so nginx can access it also&lt;/li&gt;
&lt;li&gt;Line 41: We define limits cpu for this container&lt;/li&gt;
&lt;li&gt;Line 48: We create an nginx container from Docker hub’s nginx office image.&lt;/li&gt;
&lt;li&gt;Line 51: We mount the shared volume as we do with the php-fpm container&lt;/li&gt;
&lt;li&gt;Line 53: We mount the config mapped above into nginx config file path&lt;/li&gt;
&lt;li&gt;Line 63: We expose port 80 to the Pod&lt;/li&gt;
&lt;li&gt;Line 64: We define a healthcheck endpoint (HTTP GET /) to tell k8s this Pod is running well or not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s apply it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f deployment.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expose pods with Load Balancer&lt;/strong&gt;&lt;br&gt;
After applying the Deployment above, k8s will begin to create Pods and exposing them inside k8s network. Now we’d like to expose them via a single Load balancer endpoint.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;service-loadbalancer.yml&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: service-loadbalancer
spec:
  selector:
    name: templated-pod
  ports:
    - port: 80
      targetPort: 80

  type: LoadBalancer
  externalIPs:
    - 192.168.1.33
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As told above, all Pods created with the deployment will be tagged as name = templated-pod. We just need to create a Service (Line 2) with type LoadBalancer (Line 12), tell it to balance the traffic to all Pods tagged with name = templated-pod (Line 6 &amp;amp; 7), via the port 80 (Line 9 &amp;amp; 10)&lt;/p&gt;

&lt;p&gt;Apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f service-loadbalancer.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying the Load balancer, you can start to see your application, by browsing &lt;a href="http://192.168.1.33"&gt;http://192.168.1.33&lt;/a&gt; (the Master kube’s IP), great!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-scale pods with Horizontal Pod Autoscaler&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s tune our infrastructure by adding a Horizontal Pod Autoscaler. It will automatic scale up / down our Pods depends on CPU/RAM/… usage. Let’s say you want to add 10 minions when traffic is high (CPU &amp;gt; 50%, for example), and reduce to just 3 minions when traffic is low.&lt;/p&gt;

&lt;p&gt;Create file &lt;code&gt;hpa.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa
spec:
  maxReplicas: 10
  minReplicas: 3
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: deployment
  targetCPUUtilizationPercentage: 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing special here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line 6: Max minions we’re willing to have&lt;/li&gt;
&lt;li&gt;Line 7: Minimum minions to serve the website&lt;/li&gt;
&lt;li&gt;Line 10-11: Select the target for scaling: we select the Deployment controller, named deployment, as created above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f hpa.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Play time
&lt;/h1&gt;

&lt;p&gt;Open the Kubernetes dashboard we installed above and start monitoring our k8s:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;: Try to make a change to the index.php file, then rebuild &amp;amp; push it with tag &lt;code&gt;v2&lt;/code&gt;. Then change the &lt;code&gt;deployment.yml&lt;/code&gt; (Line 32) to use &lt;code&gt;192.168.1.33:5000/martinpham/kphp:v2&lt;/code&gt; &amp;amp; apply it. You will see k8s creates 2 pods with new version, while keeping 1 pod with old version &amp;amp; deleting 2 old pods. When finishes, the last old pod will be deleted &amp;amp; another new pod will be created. No downtime during rollout!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stress-test&lt;/strong&gt;: Try to send a lot of traffic into the Load balancer endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ab -n 100000 -c 10000 http://192.168.1.33/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see k8s monitor all the pod’s cpu, when it crosses 50%, k8s will create up to 10 pods to serve the traffic. Then try to stop the test &amp;amp; wait some moment to see k8s kills the pods when they’re not needed anymore. Everything will be done automatically!&lt;/p&gt;

&lt;p&gt;I’ve created a repository contains all the code we talked about here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gitlab.com/martinpham/kubernetes-fun"&gt;https://gitlab.com/martinpham/kubernetes-fun&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have fun! Thank you very much for following my first &lt;a href="//dev.to"&gt;dev.to&lt;/a&gt; tutorial!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ps.. Small update: I've create &lt;a href="https://dev.to/martinpham/secure-your-kubernetes-application-with-https-ng7"&gt;an additional tutorial for HTTPS on top of our infrastructure here&lt;/a&gt;, hope you like it!&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Having fun with Kubernetes - Chapter 4</title>
      <dc:creator>Martin Pham</dc:creator>
      <pubDate>Mon, 23 Dec 2019 16:19:44 +0000</pubDate>
      <link>https://dev.to/martinpham/having-fun-with-kubernetes-chapter-4-1ol4</link>
      <guid>https://dev.to/martinpham/having-fun-with-kubernetes-chapter-4-1ol4</guid>
      <description>&lt;p&gt;From &lt;a href="https://www.martinpham.com/2019/12/08/having-fun-with-kubernetes-3/" rel="noopener noreferrer"&gt;previous chapter&lt;/a&gt;, we were talking about the infrastructure which we're gonna build. In this chapter, let’s install &amp;amp; setup a k8s cluster, with 2 nodes!&lt;/p&gt;

&lt;p&gt;We will start with the Master server first&lt;/p&gt;

&lt;p&gt;For this Lab, I’m gonna use Virtual machines to simulate servers. I’m using macOS Catalina 10.15.2, VMWare Fusion Pro 11.5.1, with 3 Ubuntu 18.04 VMs (2 CPUs, 1GB RAM, bridged network):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kube: master (IP: 192.168.1.33)&lt;/li&gt;
&lt;li&gt;kube1: node 1 (IP: 192.168.1.34)&lt;/li&gt;
&lt;li&gt;kube2: node 2 (IP: 192.168.1.35)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before everything, note that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes won’t run if swap enabled&lt;/li&gt;
&lt;li&gt;Kubernetes master-nodes communication will require some ports opened&lt;/li&gt;
&lt;li&gt;Kubernetes could face some problems with SELINUX&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Alright, let’s start with the Master VM.&lt;/p&gt;

&lt;h1&gt;
  
  
  Master component
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo su
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Disable swap&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Also don’t forget to disable swap on reboot, by editing &lt;code&gt;/etc/fstab&lt;/code&gt; file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(Optional) Set hostname&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ hostnamectl set-hostname kube
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;(Optional) Set static IP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit file &lt;code&gt;/etc/netplan/50-cloud-init.yaml&lt;/code&gt; to set static IP&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/netplan/50-cloud-init.yaml
network:
    renderer: networkd
    ethernets:
        ens33:
            dhcp4: no
            addresses: [192.168.1.33/24]
            gateway4: 192.168.1.1
            nameservers:
                addresses: [8.8.8.8,8.8.4.4]
    version: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Update apt&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install Docker&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ apt install docker.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Auto start Docker&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ systemctl enable docker &amp;amp;&amp;amp; systemctl start docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install Kubeadm&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add

$ apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"

$ apt install kubeadm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Init Kubeadm&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubeadm init --pod-network-cidr=10.244.0.0/16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Kubeadm inited, it will give you a command with token to run it on Node servers. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubeadm join 192.168.1.33:6443 --token xxx --discovery-token-ca-cert-hash xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create k8s config place&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create Virtual network&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re gonna use flannel for Virtual network&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;(Optional) Create Docker registry&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this Lab, we’re gonna setup a local insecure Docker registry to store our built images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker run -d -p 5000:5000 --name registry registry:2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For security reasons, Docker doesn’t want to connect to an insecure registry.&lt;/p&gt;

&lt;p&gt;To allow Docker to use insecure registry, you need to:&lt;/p&gt;

&lt;p&gt;Edit file &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/docker/daemon.json
{
        "insecure-registries" : ["192.168.1.33:5000"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or file &lt;code&gt;/etc/default/docker&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/default/docker
DOCKER_OPTS="--insecure-registry 192.168.1.33:5000"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Docker&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;(Remember 192.168.1.33 is our Master server’s IP)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install metrics server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd /etc
$ git clone https://github.com/kubernetes-incubator/metrics-server.git
$ cd metrics-server/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit file &lt;code&gt;/etc/metrics-server/deploy/1.8+/metrics-server-deployment.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      - name: metrics-server
        image: k8s.gcr.io/metrics-server-amd64:v0.3.6
        args:
          - --cert-dir=/tmp
          - --secure-port=4443
        command:
          - /metrics-server
          - --metric-resolution=5s
          - --kubelet-insecure-tls
          - --kubelet-preferred-address-types=InternalIP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f /etc/metrics-server/deploy/1.8+/metrics-server-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! you have a working Master server now. Now we're gonna setup the Node servers and connect them together, just 5 minutes ahead :)&lt;/p&gt;




&lt;h1&gt;
  
  
  Node component
&lt;/h1&gt;

&lt;p&gt;Let’s start on Kube1 (192.168.1.34)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo su
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Disable swap&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;To disable swap on reboot, edit &lt;code&gt;/etc/fstab&lt;/code&gt; file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;(Optional) Set hostname&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ hostnamectl set-hostname kube1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;(Optional) Set static IP&lt;/strong&gt;&lt;br&gt;
Edit file &lt;code&gt;/etc/netplan/50-cloud-init.yaml&lt;/code&gt; to set static IP&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/netplan/50-cloud-init.yaml
network:
    renderer: networkd
    ethernets:
        ens33:
            dhcp4: no
            addresses: [192.168.1.34/24]
            gateway4: 192.168.1.1
            nameservers:
                addresses: [8.8.8.8,8.8.4.4]
    version: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Update apt&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install Docker&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ apt install docker.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;**Auto start Docker&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ systemctl enable docker &amp;amp;&amp;amp; systemctl start docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install Kubeadm&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add

$ apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"

$ apt install kubeadm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Join this node to the master&lt;/strong&gt;&lt;br&gt;
This command was generated when you setup your Master server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubeadm join xxx:6443 --token xxx --discovery-token-ca-cert-hash xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;(Optional) Docker registry&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As I told, for some security reasons, Docker doesn’t want to connect to an insecure registry.&lt;/p&gt;

&lt;p&gt;To allow Docker to use insecure registry, you need to:&lt;/p&gt;

&lt;p&gt;Edit file &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/docker/daemon.json
{
        "insecure-registries" : ["192.168.1.33:5000"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or edit file &lt;code&gt;/etc/default/docker&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/default/docker
DOCKER_OPTS="--insecure-registry 192.168.1.33:5000"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Docker&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Repeat the same, for Kube2 (don’t forget the hostname and static IP)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Done!&lt;/strong&gt; Now you have a working k8s cluster with Master and 2 Nodes, in the final chapter, we will build our infrastructure on this cluter. And trust me, &lt;strong&gt;it's not harder than playing with Lego blocks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6687oq714zg04y0qar1m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6687oq714zg04y0qar1m.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final chapter is &lt;a href="https://www.martinpham.com/2019/12/14/having-fun-with-kubernetes-final/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>docker</category>
      <category>php</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
