<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ containerization - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ containerization - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 16:30:13 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/containerization/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build Microservices-Based REST APIs for Healthcare Portals ]]>
                </title>
                <description>
                    <![CDATA[ Microservices architecture enables healthcare portals to scale, secure sensitive data, and evolve rapidly. Using ASP.NET 10 and C#, you can build independent REST APIs for services like patients, appo ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-microservices-based-rest-apis-for-healthcare-portals/</link>
                <guid isPermaLink="false">69e2610cfd22b8ad6251e84b</guid>
                
                    <category>
                        <![CDATA[ REST APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ASP.NET 10 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Database per Service Pattern ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Service Communication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gopinath Karunanithi ]]>
                </dc:creator>
                <pubDate>Fri, 17 Apr 2026 16:30:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d834b346-3fcf-442c-836c-94ed7ef8a17d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Microservices architecture enables healthcare portals to scale, secure sensitive data, and evolve rapidly.</p>
<p>Using ASP.NET 10 and C#, you can build independent REST APIs for services like patients, appointments, and authentication, each with its own database and deployment lifecycle.</p>
<p>Combined with API gateways, JWT-based security, observability, and containerization, this approach ensures reliable, maintainable, and production-ready healthcare systems.</p>
<p>In this tutorial, you’ll learn how to design and build a microservices-based healthcare portal using ASP.NET 10 and C#. We’ll cover how to structure services, implement REST APIs, secure endpoints, enable service communication, and deploy using modern containerization practices.</p>
<p>By the end, you’ll have a clear understanding of how to create scalable, secure, and production-ready healthcare systems.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-overview">Overview</a></p>
</li>
<li><p><a href="#heading-why-use-microservices-for-healthcare-portals">Why Use Microservices for Healthcare Portals?</a></p>
</li>
<li><p><a href="#heading-high-level-architecture">High-Level Architecture</a></p>
</li>
<li><p><a href="#heading-designing-rest-apis-for-healthcare-services">Designing REST APIs for Healthcare Services</a></p>
</li>
<li><p><a href="#heading-how-to-build-a-microservice-with-aspnet-10">How to Build a Microservice with ASP.NET 10</a></p>
</li>
<li><p><a href="#heading-database-per-service-pattern">Database per Service Pattern</a></p>
</li>
<li><p><a href="#heading-service-communication">Service Communication</a></p>
</li>
<li><p><a href="#heading-api-gateway-implementation">API Gateway Implementation</a></p>
</li>
<li><p><a href="#heading-implementing-security-in-healthcare-apis">Implementing Security in Healthcare APIs</a></p>
</li>
<li><p><a href="#heading-observability-and-logging">Observability and Logging</a></p>
</li>
<li><p><a href="#heading-containerization-with-docker">Containerization with Docker</a></p>
</li>
<li><p><a href="#heading-deployment-strategies">Deployment Strategies</a></p>
</li>
<li><p><a href="#heading-best-practices-with-examples">Best Practices (With Examples)</a></p>
</li>
<li><p><a href="#heading-when-not-to-use-microservices">When NOT to Use Microservices</a></p>
</li>
<li><p><a href="#heading-future-enhancements">Future Enhancements</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before getting started, you should be familiar with:</p>
<ul>
<li><p>C# and ASP.NET Core fundamentals</p>
</li>
<li><p>REST API concepts (HTTP methods, routing, status codes)</p>
</li>
<li><p>Basic understanding of microservices architecture</p>
</li>
</ul>
<p>Tools required:</p>
<ul>
<li><p>.NET 10 SDK</p>
</li>
<li><p>Visual Studio or VS Code</p>
</li>
<li><p>Postman or Swagger</p>
</li>
<li><p>Docker (optional but recommended)</p>
</li>
</ul>
<h2 id="heading-overview">Overview</h2>
<p>Healthcare portals power critical workflows such as patient registration, appointment scheduling, electronic health records (EHR), billing, and telemedicine. These systems must handle sensitive data, high availability requirements, and frequent updates.</p>
<p>Traditionally, many healthcare applications were built as monolithic systems. While simple to start with, monoliths quickly become difficult to scale, maintain, and secure. A single failure can impact the entire system, and even small changes require redeploying the entire application.</p>
<p>Microservices architecture addresses these challenges by breaking the application into smaller, independent services. Each service is responsible for a specific domain, such as patient management or appointment scheduling, and can be developed, deployed, and scaled independently.</p>
<p>In this article, you'll learn how to design and implement a microservices-based healthcare REST API using ASP.NET 10 and C#. We'll walk through architecture design, service implementation, communication patterns, security, observability, and deployment strategies.</p>
<h2 id="heading-why-use-microservices-for-healthcare-portals">Why Use Microservices for Healthcare Portals?</h2>
<p>Healthcare systems are inherently complex. They involve multiple domains such as patient records, appointments, billing, authentication and authorization. A microservices approach allows each of these domains to be handled independently. There are many benefits to this approach such as:</p>
<ul>
<li><p><strong>Scalability</strong>: Scale only the services under heavy load (for example, appointments during peak hours)</p>
</li>
<li><p><strong>Fault isolation</strong>: Failure in one service does not crash the entire system</p>
</li>
<li><p><strong>Faster deployment</strong>: Teams can deploy updates independently</p>
</li>
<li><p><strong>Improved security</strong>: Sensitive services can have stricter access controls</p>
</li>
</ul>
<p>For example, a patient service can handle personal data, while a billing service manages transactions, each with different security policies.</p>
<h2 id="heading-high-level-architecture"><strong>High-Level Architecture</strong></h2>
<p>A typical healthcare microservices architecture includes API Gateway (central entry point), microservices (Patient, Appointment, Auth), database per Service and service Communication Layer.</p>
<p>The request flow starts with the client sending a request. Then the API Gateway routes the request and the target microservice processes it. Then a response is returned. This separation ensures modularity and maintainability.</p>
<h2 id="heading-designing-rest-apis-for-healthcare-services">Designing REST APIs for Healthcare Services</h2>
<p>Designing REST APIs in a microservices architecture requires clear, consistent naming conventions so that endpoints are intuitive, predictable, and easy to consume by clients and other services.</p>
<h3 id="heading-naming-conventions">Naming Conventions</h3>
<p>REST APIs are resource-oriented, meaning URLs should represent entities (nouns), not actions (verbs). Each resource corresponds to a domain object in your system, such as patients, appointments, or billing records.</p>
<p><strong>Key principles:</strong></p>
<ul>
<li><p>Use plural nouns for resources (for example, <code>/patients</code>, <code>/appointments</code>)</p>
</li>
<li><p>Avoid verbs in URLs (don't use <code>/getPatients</code>)</p>
</li>
<li><p>Use hierarchical structure for relationships (for example, <code>/patients/{id}/appointments</code>)</p>
</li>
<li><p>Keep naming consistent across all services</p>
</li>
</ul>
<p>These conventions improve API readability, developer experience, and maintainability across teams</p>
<h4 id="heading-example-patient-api-endpoints">Example: Patient API Endpoints</h4>
<p>The following endpoints represent standard CRUD (Create, Read, Update, Delete) operations for managing patients:</p>
<pre><code class="language-plaintext">GET    /api/patients        // Retrieve all patients
GET    /api/patients/{id}   // Retrieve a specific patient
POST   /api/patients        // Create a new patient
PUT    /api/patients/{id}   // Update an existing patient
DELETE /api/patients/{id}   // Delete a patient
</code></pre>
<p>Each HTTP method defines the type of operation being performed:</p>
<ul>
<li><p>GET: Fetch data (read-only)</p>
</li>
<li><p>POST: Create new resources</p>
</li>
<li><p>PUT: Update existing resources</p>
</li>
<li><p>DELETE: Remove resources</p>
</li>
</ul>
<p>These operations follow REST standards, ensuring consistency across services and making APIs easier to integrate with frontend apps, mobile clients, or third-party healthcare systems</p>
<h3 id="heading-best-practices-for-designing-healthcare-rest-apis">Best Practices for Designing Healthcare REST APIs</h3>
<p>Designing REST APIs for healthcare systems requires more than standard conventions. It demands careful consideration of performance, data sensitivity, and interoperability.</p>
<h4 id="heading-1-use-proper-http-methods">1. Use proper HTTP methods</h4>
<p>Ensure each endpoint uses the correct HTTP verb (GET, POST, PUT, DELETE) to clearly communicate its purpose. This improves API predictability and aligns with REST standards used across healthcare platforms.</p>
<h4 id="heading-2-return-meaningful-status-codes">2. Return meaningful status codes</h4>
<p>Use appropriate HTTP status codes to indicate the result of a request. For example:</p>
<ul>
<li><p>200 OK for successful retrieval</p>
</li>
<li><p>201 Created for successful resource creation</p>
</li>
<li><p>400 Bad Request for validation errors</p>
</li>
<li><p>404 Not Found when a resource doesn’t exist<br>Clear status codes help clients handle responses correctly.</p>
</li>
</ul>
<h4 id="heading-3-implement-pagination-for-large-datasets">3. Implement pagination for large datasets</h4>
<p>Healthcare systems often deal with large volumes of data (for example, patient records, appointment logs). Use pagination to limit response size:</p>
<p><code>GET /api/patients?page=1&amp;pageSize=20</code></p>
<p>This improves performance and reduces server load.</p>
<h4 id="heading-4-use-api-versioning">4. Use API versioning</h4>
<p>Version your APIs to avoid breaking existing clients when making changes:</p>
<p><code>/api/v1/patients</code></p>
<p>This is especially important in healthcare, where integrations with external systems must remain stable over time.</p>
<h4 id="heading-5-validate-and-sanitize-input-data">5. Validate and sanitize input data</h4>
<p>Always validate incoming data to prevent errors and ensure data integrity. For example, enforce required fields like patient name, date of birth, and contact details.</p>
<h4 id="heading-6-protect-sensitive-data">6. Protect sensitive data</h4>
<p>Avoid exposing sensitive patient information unnecessarily. Use filtering, masking, or field-level access control where needed to comply with healthcare data regulations.</p>
<h4 id="heading-7-ensure-consistent-response-structure">7. Ensure consistent response structure</h4>
<p>Return responses in a standard format (for example, including data, status, and message fields). This makes APIs easier to consume and debug across multiple services.</p>
<h2 id="heading-how-to-build-a-microservice-with-aspnet-10">How to Build a Microservice with ASP.NET 10</h2>
<p>Let’s implement a simple Patient Service.</p>
<h3 id="heading-step-1-create-project">Step 1: Create Project</h3>
<p>In this step, we'll create a new <a href="http://ASP.NET">ASP.NET</a> Web API project that will serve as our Patient microservice. This project provides the foundation for defining endpoints, handling HTTP requests, and structuring our service independently from other parts of the system.</p>
<pre><code class="language-shell">dotnet new webapi -n PatientService
cd PatientService
</code></pre>
<h3 id="heading-step-2-define-model">Step 2: Define Model</h3>
<p>Next, we'll define a simple data model representing a patient. Models define the structure of the data your API will send and receive, and they typically map to database entities in real-world applications.</p>
<pre><code class="language-csharp">public class Patient
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
</code></pre>
<h3 id="heading-step-3-create-controller">Step 3: Create Controller</h3>
<p>Here, we're creating a controller to handle incoming HTTP requests. Controllers define API endpoints and contain the logic for processing requests, interacting with data, and returning responses to clients.</p>
<pre><code class="language-csharp">[ApiController]
[Route("api/patients")]
public class PatientController : ControllerBase
{
    private static List&lt;Patient&gt; patients = new();

    [HttpGet]
    public IActionResult GetPatients()
    {
        return Ok(patients);
    }

    [HttpPost]
    public IActionResult AddPatient(Patient patient)
    {
        patients.Add(patient);
        return CreatedAtAction(nameof(GetPatients), patient);
    }
}
</code></pre>
<h2 id="heading-database-per-service-pattern">Database per Service Pattern</h2>
<p>Each microservice should manage its own database to ensure loose coupling and independent operation. This allows services to evolve, scale, and be deployed without affecting others. It also improves data isolation and aligns with the core principles of microservices architecture.</p>
<p>Here's an example with Entity Framework Core:</p>
<pre><code class="language-csharp">public class PatientDbContext : DbContext
{
    public PatientDbContext(DbContextOptions&lt;PatientDbContext&gt; options)
        : base(options) { }

    public DbSet&lt;Patient&gt; Patients { get; set; }
}
</code></pre>
<p>This matters because it avoids cross-service dependencies, enables independent scaling, and improves data security, making microservices more efficient and secure.</p>
<h2 id="heading-service-communication">Service Communication</h2>
<p>Microservices communicate with each other to share data and coordinate workflows across the system. This communication can be handled through synchronous requests or asynchronous messaging, depending on the use case.</p>
<p>Choosing the right approach helps ensure scalability, reliability, and responsiveness in distributed systems</p>
<h3 id="heading-1-synchronous-communication-http">1. Synchronous Communication (HTTP)</h3>
<pre><code class="language-csharp">var response = await httpClient.GetAsync("http://appointment-service/api/appointments");
</code></pre>
<h3 id="heading-2-asynchronous-communication-messaging">2. Asynchronous Communication (Messaging)</h3>
<p>Using message brokers like RabbitMQ:</p>
<ul>
<li><p>Services publish events</p>
</li>
<li><p>Other services consume them</p>
</li>
</ul>
<p><strong>Example:</strong></p>
<p>When a patient registers, an event triggers an appointment service.</p>
<h2 id="heading-api-gateway-implementation"><strong>API Gateway Implementation</strong></h2>
<p>An API Gateway acts as the central entry point for all client requests in a microservices architecture. It handles routing, authentication, and request aggregation, simplifying how clients interact with multiple services. This layer helps improve security, scalability, and overall system management.</p>
<p>Here's an example (Ocelot configuration):</p>
<pre><code class="language-json">{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/patients",
      "UpstreamPathTemplate": "/patients",
      "DownstreamHostAndPorts": [
        { "Host": "localhost", "Port": 5001 }
      ]
    }
  ]
}
</code></pre>
<p>Benefits include centralized routing, authentication handling, and rate limiting</p>
<h2 id="heading-implementing-security-in-healthcare-apis">Implementing Security in Healthcare APIs</h2>
<p>Security is critical in healthcare systems due to the sensitive nature of patient data. APIs must enforce strong authentication, authorization, and data protection mechanisms. Proper security ensures compliance, prevents unauthorized access, and safeguards user trust.</p>
<h3 id="heading-1-jwt-authentication">1. JWT Authentication</h3>
<pre><code class="language-csharp">builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =&gt;
    {
        options.Authority = "https://auth-server";
        options.Audience = "healthcare-api";
    });
</code></pre>
<p>JWT (JSON Web Token) authentication is used to verify the identity of users accessing the API.</p>
<p>The authentication scheme ("Bearer") tells the API to expect a token in the Authorization header: <code>Authorization: Bearer &lt;token&gt;</code></p>
<p>Authority represents the trusted authentication server (identity provider) that issues tokens.</p>
<p>And audience ensures that the token is intended specifically for this API.</p>
<p>When a request is made, the API:</p>
<ol>
<li><p>Extracts the JWT from the request header</p>
</li>
<li><p>Validates its signature using the authority</p>
</li>
<li><p>Checks claims like expiration and audience</p>
</li>
<li><p>Grants access only if the token is valid</p>
</li>
</ol>
<p>This ensures that only authenticated users can access healthcare services.</p>
<h3 id="heading-2-role-based-authorization">2. Role-Based Authorization</h3>
<pre><code class="language-csharp">[Authorize(Roles = "Doctor")]
public IActionResult GetSensitiveData()
{
    return Ok();
}
</code></pre>
<p>Role-based authorization restricts access based on user roles.</p>
<ul>
<li><p>The <code>[Authorize]</code> attribute enforces that only authenticated users can access the endpoint.</p>
</li>
<li><p>The <code>Roles = "Doctor"</code> condition ensures that only users with the Doctor role can access this resource.</p>
</li>
</ul>
<p>When a user sends a request:</p>
<ol>
<li><p>Their JWT token is validated</p>
</li>
<li><p>The system checks the role claim inside the token</p>
</li>
<li><p>Access is granted only if the required role matches</p>
</li>
</ol>
<p>This is critical in healthcare systems where doctors access medical records, admins manage system data, and patients access only their own information.</p>
<h3 id="heading-3-secure-secrets-management">3. Secure Secrets Management</h3>
<pre><code class="language-csharp">var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION");
</code></pre>
<p>Sensitive configuration data such as database connection strings should never be hardcoded in the application.</p>
<p><code>Environment.GetEnvironmentVariable()</code> retrieves secrets securely from the environment. These values are typically stored in:</p>
<ul>
<li><p>Environment variables</p>
</li>
<li><p>Secret managers (Azure Key Vault, AWS Secrets Manager)</p>
</li>
<li><p>Container orchestration platforms</p>
</li>
</ul>
<p>Benefits:</p>
<ul>
<li><p>Prevents exposure of credentials in source code</p>
</li>
<li><p>Supports secure deployments across environments</p>
</li>
<li><p>Simplifies secret rotation without code changes</p>
</li>
</ul>
<h3 id="heading-4-enforce-https">4. Enforce HTTPS</h3>
<pre><code class="language-csharp">app.UseHttpsRedirection();
</code></pre>
<p>HTTPS ensures that all communication between the client and server is encrypted.</p>
<p><code>UseHttpsRedirection()</code> automatically redirects HTTP requests to HTTPS. This protects sensitive healthcare data (such as patient records and credentials) from Man-in-the-Middle attacks, data interception, and unauthorized access.</p>
<p>In healthcare systems, encryption is essential for compliance with data protection standards and regulations.</p>
<p>Together, these security mechanisms provide multiple layers of protection:</p>
<ul>
<li><p>Authentication verifies identity</p>
</li>
<li><p>Authorization controls access</p>
</li>
<li><p>Secrets management protects credentials</p>
</li>
<li><p>HTTPS secures data in transit</p>
</li>
</ul>
<p>This layered approach is essential for safeguarding sensitive healthcare data and ensuring compliance with industry standards.</p>
<h2 id="heading-observability-and-logging"><strong>Observability and Logging</strong></h2>
<p>Observability enables you to monitor system health, diagnose issues, and understand how services interact in real time. By implementing logging, metrics, and tracing, teams can quickly identify failures and performance bottlenecks. This is essential for maintaining reliability in distributed systems.</p>
<p>Here's a basic logging example:</p>
<pre><code class="language-csharp">_logger.LogInformation("Fetching patients");
</code></pre>
<p>This line writes an informational log entry whenever the patient data is being retrieved. The _logger instance is part of ASP.NET’s built-in logging framework and is typically injected into the class through dependency injection.</p>
<p>Logging at this level helps developers trace normal application behavior and understand when specific operations occur, which is especially useful during debugging and monitoring in production environments.</p>
<h3 id="heading-application-insights-integration">Application Insights Integration</h3>
<pre><code class="language-csharp">builder.Services.AddApplicationInsightsTelemetry();
</code></pre>
<p>This configuration enables integration with Application Insights, a cloud-based monitoring service. By adding this line, the application automatically collects telemetry data such as request rates, response times, failure rates, and dependency calls. This allows teams to monitor the health of the application in real time and quickly identify performance bottlenecks or failures across distributed microservices.</p>
<h3 id="heading-custom-metrics">Custom Metrics</h3>
<pre><code class="language-csharp">var telemetryClient = new TelemetryClient();
telemetryClient.TrackMetric("PatientsFetched", 1);
</code></pre>
<p>Here, a TelemetryClient instance is used to send custom metrics to the monitoring system. The TrackMetric method records a numerical value –&nbsp;in this case, tracking how many times patients are fetched.</p>
<p>Custom metrics like this help measure business-specific operations and provide deeper insight into how the system is being used beyond standard performance metrics.</p>
<h3 id="heading-health-checks">Health Checks</h3>
<pre><code class="language-csharp">app.MapHealthChecks("/health");
</code></pre>
<p>This line exposes a health check endpoint at /health that external systems can use to verify whether the service is running correctly. When this endpoint is called, it returns the status of the application and any configured dependencies, such as databases or external services.</p>
<p>Health checks are commonly used by load balancers, container orchestrators, and monitoring tools to automatically detect failures and restart or reroute traffic if needed.</p>
<p>Together, logging, telemetry, custom metrics, and health checks provide a complete observability strategy. They allow teams to understand system behavior, detect issues early, and maintain reliability across distributed healthcare services where uptime and performance are critical.</p>
<h2 id="heading-containerization-with-docker">Containerization with Docker</h2>
<p>Containerization allows microservices to run in isolated and consistent environments across development and production. Using Docker, you can package applications with all dependencies, ensuring portability and easier deployment. This approach simplifies scaling and infrastructure management.</p>
<p>The following Dockerfile shows a minimal setup for packaging the Patient Service into a container image:</p>
<pre><code class="language-dockerfile">FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "PatientService.dll"]
</code></pre>
<p>This Dockerfile defines how the Patient Service is packaged into a container image so it can run consistently across different environments.</p>
<p>The <strong>FROM</strong> instruction specifies the base image, which in this case is the official ASP.NET runtime image for .NET 10. This image includes all the necessary runtime components required to execute the application, so you don’t need to install .NET separately inside the container.</p>
<p>The <strong>WORKDIR /app</strong> line sets the working directory inside the container. All subsequent commands will run relative to this directory, helping organize application files in a predictable structure.</p>
<p>The <strong>COPY . .</strong> instruction copies all files from the current project directory on your machine into the container’s working directory. This includes the compiled application binaries and any required resources.</p>
<p>Finally, the <strong>ENTRYPOINT</strong> defines the command that runs when the container starts. In this case, it launches the PatientService application using the .NET runtime.</p>
<p>Together, these steps package the microservice into a portable unit that can be deployed consistently across development, staging, and production environments. This ensures that the application behaves the same regardless of where it is deployed, which is a key advantage of containerization in microservices architectures.</p>
<h2 id="heading-deployment-strategies"><strong>Deployment Strategies</strong></h2>
<p>Deploying microservices requires strategies that minimize downtime and reduce risk during updates.</p>
<p>Techniques like rolling updates, canary releases, and blue-green deployments help ensure smooth transitions. These approaches improve system stability and user experience during releases.</p>
<h3 id="heading-key-strategies">Key Strategies</h3>
<p>Deploying microservices requires strategies that minimize downtime, reduce risk, and ensure system stability –&nbsp;especially in healthcare systems where availability and data integrity are critical.</p>
<h4 id="heading-1-rolling-updates">1. Rolling Updates</h4>
<p>Rolling updates deploy changes gradually by updating instances of a service one at a time instead of all at once. As new versions are deployed, old instances are terminated in phases, ensuring that the system remains available throughout the process.</p>
<p>This approach works well for stateless services and is commonly used in container orchestration platforms. It allows continuous availability while still enabling safe deployment of new features.</p>
<p>Rolling updates are best used when:</p>
<ul>
<li><p>You want zero downtime deployments</p>
</li>
<li><p>Backward compatibility between versions is maintained</p>
</li>
<li><p>Changes are relatively low risk</p>
</li>
</ul>
<h4 id="heading-2-canary-deployments">2. Canary Deployments</h4>
<p>Canary deployments release a new version of a service to a small subset of users before rolling it out to everyone. This allows teams to monitor the behavior of the new version in a real-world environment with limited exposure.</p>
<p>If issues are detected, the deployment can be rolled back quickly without affecting the majority of users.</p>
<p>Canary deployments are ideal when:</p>
<ul>
<li><p>Releasing high-risk or complex features</p>
</li>
<li><p>Testing performance under real traffic</p>
</li>
<li><p>Gradually validating new functionality</p>
</li>
</ul>
<h4 id="heading-3-blue-green-deployments">3. Blue-Green Deployments</h4>
<p>Blue-green deployment involves maintaining two identical environments: one running the current version (blue) and one running the new version (green). Traffic is switched from blue to green once the new version is fully tested and ready.</p>
<p>If something goes wrong, traffic can be immediately switched back to the previous version.</p>
<p>This strategy is particularly useful when:</p>
<ul>
<li><p>You need instant rollback capability</p>
</li>
<li><p>System stability is critical</p>
</li>
<li><p>Downtime must be completely avoided</p>
</li>
</ul>
<h3 id="heading-choosing-the-right-strategy-for-healthcare-microservices">Choosing the Right Strategy for Healthcare Microservices</h3>
<p>In a healthcare portal, where reliability and patient data integrity are essential, blue-green deployments are often the safest choice. They allow full validation of the new version before exposing it to users and provide immediate rollback in case of failure.</p>
<p>But rolling updates are also commonly used for routine updates where backward compatibility is ensured, while canary deployments are useful when introducing new features like AI diagnostics or analytics modules.</p>
<h4 id="heading-example-blue-green-deployment-with-containers">Example: Blue-Green Deployment with Containers</h4>
<p>Let’s walk through a simple conceptual example using containers.</p>
<p>Assume you have two environments:</p>
<ul>
<li><p>Blue (current version) running PatientService v1</p>
</li>
<li><p>Green (new version) running PatientService v2</p>
</li>
</ul>
<p>First, you deploy the new version (v2) alongside the existing one without affecting users.</p>
<p>Then you run tests and verify that the new version behaves correctly.</p>
<p>After that, you update the load balancer or API gateway to route traffic from blue to green. Then you monitor the system for errors or performance issues.</p>
<p>If everything is stable, you keep green as the active environment. If not, switch traffic back to blue instantly.</p>
<p>In a real-world setup, this traffic switching is typically handled by:</p>
<ul>
<li><p>API Gateways</p>
</li>
<li><p>Load balancers</p>
</li>
<li><p>Kubernetes services</p>
</li>
</ul>
<p>This approach ensures that users experience no downtime while giving teams full control over deployment risk.</p>
<p>In practice, many production systems combine these strategies –&nbsp;for example, starting with a canary release and then completing deployment with a rolling update – to balance risk and efficiency.</p>
<h2 id="heading-best-practices-with-examples">Best Practices (With Examples)</h2>
<p>Designing reliable microservices for healthcare systems requires applying proven patterns that improve stability, maintainability, and resilience. Below are some key best practices with practical examples.</p>
<h3 id="heading-1-use-api-versioning">1. Use API Versioning</h3>
<p>API versioning ensures backward compatibility when your service evolves. In healthcare systems, where integrations with external systems (labs, insurance, EHR) are common, breaking changes can cause serious issues.</p>
<p>Here's an example:</p>
<pre><code class="language-csharp">[Route("api/v1/patients")]
</code></pre>
<p>This route attribute defines the base URL for the API and explicitly includes a version identifier (v1). By embedding the version in the route, the service can support multiple versions of the same API simultaneously. This allows existing clients to continue using older versions while newer versions are introduced without breaking compatibility.</p>
<p>You can later introduce a new version:</p>
<pre><code class="language-csharp">[Route("api/v2/patients")]
</code></pre>
<p>This represents a newer version of the same API with potentially updated functionality or structure. By separating versions at the routing level, developers can evolve the API safely while giving clients time to migrate.</p>
<p>This approach is especially important in healthcare systems where external integrations must remain stable over long periods.</p>
<p>This allows safe rollout of new features, support for legacy clients and gradual migration between versions.</p>
<h3 id="heading-2-implement-retry-policies">2. Implement Retry Policies</h3>
<p>Network calls between microservices can fail due to transient issues such as timeouts or temporary service unavailability. Retry policies help automatically recover from such failures.</p>
<p>Here's an example (using Polly):</p>
<pre><code class="language-csharp">services.AddHttpClient("api")
    .AddTransientHttpErrorPolicy(p =&gt; p.RetryAsync(3));
</code></pre>
<p>This code configures an HTTP client with a retry policy using <a href="https://www.pollydocs.org/">Polly</a>, a .NET resilience and transient-fault-handling library. Polly allows developers to define policies such as retries, circuit breakers, and timeouts for handling unreliable network calls.</p>
<p>The <code>AddTransientHttpErrorPolicy</code> method applies a retry strategy for temporary failures such as network timeouts or server errors. The <code>RetryAsync(3)</code> configuration means that if a request fails due to a transient issue, it will automatically be retried up to three times before returning an error.</p>
<p>This improves system reliability by handling temporary issues without requiring manual intervention.</p>
<p>This configuration retries failed requests up to three times before failing.</p>
<p>You can also add exponential backoff:</p>
<pre><code class="language-csharp">.AddTransientHttpErrorPolicy(p =&gt;
    p.WaitAndRetryAsync(3, retryAttempt =&gt;
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
</code></pre>
<p>This configuration enhances the retry mechanism by introducing exponential backoff. Instead of retrying immediately, the system waits progressively longer between each retry attempt.</p>
<p>Exponential backoff means:</p>
<ul>
<li><p>The first retry waits for 2¹ seconds</p>
</li>
<li><p>The second retry waits for 2² seconds</p>
</li>
<li><p>The third retry waits for 2³ seconds</p>
</li>
</ul>
<p>This approach reduces pressure on failing services and avoids overwhelming them with repeated requests. It's particularly useful in distributed systems where temporary failures are common and services need time to recover.</p>
<p>This helps in improving reliability, reducing temporary failures and avoiding manual retries.</p>
<h3 id="heading-3-enforce-input-validation">3. Enforce Input Validation</h3>
<p>Validating incoming data is critical, especially in healthcare systems where incorrect data can lead to serious consequences.</p>
<p>Here's an example:</p>
<pre><code class="language-csharp">if (string.IsNullOrEmpty(patient.Name))
    return BadRequest("Name is required");
</code></pre>
<p>This is a simple manual validation check that ensures the Name field is provided before processing the request. If the value is missing or empty, the API immediately returns a <code>BadRequest</code> response, preventing invalid data from entering the system.</p>
<p>A better approach is using data annotations:</p>
<pre><code class="language-csharp">public class Patient
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }
}
</code></pre>
<p>This example uses data annotations to enforce validation rules at the model level. The [Required] attribute ensures that the Name property must be provided when a request is made. ASP.NET automatically validates the model during request processing and returns an error response if validation fails.</p>
<p>This approach is more scalable and maintainable than manual checks, especially in larger applications.</p>
<p>This ensures clean and valid data, reduced runtime errors, and better API usability.</p>
<h3 id="heading-4-use-circuit-breaker-pattern">4. Use Circuit Breaker Pattern</h3>
<p>The circuit breaker pattern prevents cascading failures when a dependent service is down or slow.</p>
<p>For example, if the Appointment Service is unavailable, repeated calls from the Patient Service can overload the system. A circuit breaker stops these calls temporarily.</p>
<p>Here's an example (again using Polly):</p>
<pre><code class="language-csharp">services.AddHttpClient("api")
    .AddTransientHttpErrorPolicy(p =&gt;
        p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
</code></pre>
<p>This means:</p>
<ul>
<li><p>After 5 consecutive failures, the circuit opens</p>
</li>
<li><p>No further requests are sent for 30 seconds</p>
</li>
<li><p>System gets time to recover</p>
</li>
</ul>
<p>This helps in protecting system stability, preventing resource exhaustion, and improving overall resilience.</p>
<p>These practices ensure your microservices are backward-compatible (versioning), resilient (retry + circuit breaker), and reliable (validation).</p>
<p>In healthcare systems, where uptime and data integrity are critical, applying these patterns is essential.</p>
<p>This code configures a circuit breaker policy using Polly to protect the system from repeated failures when calling external services.</p>
<p>The <code>CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))</code> configuration means that if five consecutive requests fail, the circuit will open and block further requests for 30 seconds. During this time, the system will not attempt to call the failing service, allowing it time to recover.</p>
<p>After the break period, the circuit enters a half-open state where a limited number of requests are allowed to test if the service has recovered. If successful, normal operation resumes. Otherwise, the circuit opens again.</p>
<p>This pattern prevents cascading failures, reduces unnecessary load on failing services, and improves overall system resilience.</p>
<p>These examples demonstrate how small design decisions (like versioning, retries, validation, and fault handling) can significantly improve the reliability and maintainability of microservices, especially in healthcare systems where failures can have serious consequences.</p>
<h2 id="heading-when-not-to-use-microservices">When NOT to Use Microservices</h2>
<p>Microservices are powerful, but they're not a universal solution. In many cases, adopting microservices too early can introduce unnecessary complexity instead of solving real problems.</p>
<p>Before choosing this architecture, it’s important to understand when a simpler approach—such as a monolith—is more appropriate.</p>
<h3 id="heading-1-when-the-application-is-small">1. When the Application Is Small</h3>
<p>If your application has limited functionality (for example, a basic patient registration system or internal tool), splitting it into multiple services adds unnecessary overhead.</p>
<p>A monolithic architecture allows you to develop faster with less setup, debug issues more easily, and avoid managing multiple deployments.</p>
<p><strong>Example:</strong> A simple clinic portal with only patient registration and appointment booking doesn't require separate services for each feature.</p>
<h3 id="heading-2-when-the-team-size-is-limited">2. When the Team Size Is Limited</h3>
<p>When the team size is limited, microservices can become challenging. Managing multiple codebases, handling service communication, and dealing with deployments and monitoring can slow down development, making it tough for small teams to handle the complexity.</p>
<p><strong>Example:</strong> A team of 2–3 developers may spend more time managing infrastructure than building features if microservices are used prematurely.</p>
<h3 id="heading-3-when-deployment-complexity-outweighs-benefits">3. When Deployment Complexity Outweighs Benefits</h3>
<p>Microservices introduce operational complexity, including API gateways, service discovery, container orchestration (for example, Kubernetes), and monitoring and logging across services.</p>
<p>If your application doesn't require independent scaling or frequent deployments, this complexity may not be justified.</p>
<p><strong>Example:</strong> If all components of your system scale together and are updated at the same time, a monolith is often more efficient.</p>
<h3 id="heading-4-when-domain-boundaries-arent-clear">4. When Domain Boundaries Aren't Clear</h3>
<p>Microservices rely on well-defined service boundaries. If your domain isn't clearly understood, splitting into services too early can lead to tight coupling between services, frequent cross-service changes, and poorly designed APIs.</p>
<p>In such cases, starting with a monolith and refactoring later is a better approach.</p>
<h3 id="heading-5-when-you-lack-devops-and-observability-maturity">5. When You Lack DevOps and Observability Maturity</h3>
<p>Microservices require strong DevOps practices, including CI/CD pipelines, centralized logging, distributed tracing and monitoring &amp; alerting. Without these, debugging issues becomes extremely difficult.</p>
<h2 id="heading-future-enhancements"><strong>Future Enhancements</strong></h2>
<p>Healthcare systems are evolving rapidly, and microservices architectures can adapt to support new capabilities. Future improvements may include:</p>
<h3 id="heading-1event-driven-architecture">1.Event-Driven Architecture</h3>
<p>Adopting an event-driven approach allows services to communicate asynchronously through events rather than direct requests. This improves scalability, responsiveness, and fault tolerance, making it easier to handle high volumes of patient data and real-time updates across multiple services.</p>
<h3 id="heading-2-ai-powered-diagnostics">2. AI-Powered Diagnostics</h3>
<p>Integrating AI and machine learning can enhance diagnostic capabilities by analyzing patient data, detecting patterns, and providing predictive insights. This can improve clinical decision-making and streamline workflows within the healthcare portal.</p>
<h3 id="heading-3integration-with-fhir-standards">3.Integration with FHIR Standards</h3>
<p>Supporting FHIR (Fast Healthcare Interoperability Resources) standards enables seamless data exchange between different healthcare systems, labs, and third-party applications. Standardized APIs ensure better interoperability, compliance, and easier integration with external platforms.</p>
<h3 id="heading-4real-time-analytics">4.Real-Time Analytics</h3>
<p>Real-time analytics allows healthcare providers to monitor patient data, system performance, and operational metrics continuously. This supports proactive decision-making, early detection of anomalies, and improved overall quality of care.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Microservices-based REST API development provides a powerful foundation for building scalable and secure healthcare portals. By breaking applications into independent services, teams can achieve better scalability, faster deployments, and improved fault isolation.</p>
<p>However, adopting microservices is not just a technical shift—it is an architectural and operational commitment. Developers should start small, identify clear service boundaries, and gradually evolve their systems.</p>
<p>As your application grows, focus on strengthening security, improving observability, and automating deployments. These practices will ensure your healthcare platform remains reliable, compliant, and ready to scale in a cloud-native world.</p>
<p>The next step is to build your first microservice, deploy it using containers, and incrementally expand your system into a fully distributed healthcare platform.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Run a Docker Container in AWS Lambda ]]>
                </title>
                <description>
                    <![CDATA[ While containers are quite lightweight and provide various benefits, it can be challenging to decide how best to deploy them. There are a number of ways to deploy and run Docker containers. But some are best for orchestrating and managing containers,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-run-a-docker-container-in-aws-lambda/</link>
                <guid isPermaLink="false">694c7990b7478745bce04604</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ serverless ]]>
                    </category>
                
                    <category>
                        <![CDATA[ lambda ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecr ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Agnes Olorundare ]]>
                </dc:creator>
                <pubDate>Wed, 24 Dec 2025 23:38:56 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766599506861/86c07e37-7838-4186-971e-29722ccec785.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>While containers are quite lightweight and provide various benefits, it can be challenging to decide how best to deploy them. There are a number of ways to deploy and run Docker containers. But some are best for orchestrating and managing containers, and may not suit a simple use case of running just one container.</p>
<p>In this article, I’ll teach you how you can deploy a single Docker container using a serverless service on AWS called Lambda.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisite-requirements">Prerequisite/ Requirements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-serverless-with-aws-lambda">Serverless with AWS Lambda</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-run-and-test-a-container-locally">How to Build, Run, and Test a Container Locally</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-push-your-image-to-amazon-elastic-container-registry-ecr">How to Push Your Image to Amazon Elastic Container Registry (ECR)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-deploy-your-docker-image-to-lambda">How to Deploy Your Docker Image to Lambda</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cleanup">Cleanup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisite-requirements">Prerequisite/ Requirements</h2>
<p>The following tools and skills are necessary for following along with this tutorial:</p>
<ul>
<li><p>Knowledge of Docker, and have Docker installed locally.</p>
</li>
<li><p>An AWS account with credentials with administrative privilege for making API calls via the CLI. Best practice would be to limit the privilege to exactly what needs to be done.</p>
</li>
<li><p>AWS CLI installed locally</p>
</li>
<li><p>Python virtual environment managers <a target="_blank" href="https://github.com/astral-sh/uv">such as uv</a> (optional)</p>
</li>
</ul>
<h2 id="heading-serverless-with-aws-lambda">Serverless with AWS Lambda</h2>
<p>Containers provide a lightweight, consistent, and resource-friendly way of running applications. Serverless takes away the overhead of managing the underlying infrastructures on which the container runs. So as you can probably start to see, combining these tools helps you deploy applications in a way that lets you focus on business logic, performance, and what gives your product a competitive edge/ advantage.</p>
<p>One AWS tool that enables you to go serverless is Lambda. With Lambda, you’re only billed for the number of times the code in the function runs, the memory you selected at the time of provisioning the service, and the duration of each invocation of the function.</p>
<p>In addition to removing operational overhead, Lambda can also help you save money since you won’t have to deal with idle resources. The function only comes alive when triggered by a request sent to it.</p>
<h2 id="heading-how-to-build-run-and-test-a-container-locally">How to Build, Run, and Test a Container Locally</h2>
<p>Docker is a tool that helps you package applications or software into portable, standardized and shareable units that have everything the applications need such as libraries, runtime, system tools, application code, in order to run. These units are called containers.</p>
<p>In this section, I’ll walk you through building the Docker image, running the container, and testing it after it’s running.</p>
<p>You can find the project that you’ll be using here in this <a target="_blank" href="https://github.com/Agnes4Him/freecodecamp-lambda-docker">GitHub repository</a>.</p>
<h3 id="heading-build-the-docker-image">Build the Docker Image</h3>
<p>To run a Docker container, you first need to build an image. The image becomes the template or <code>class</code> from which you create the container or <code>instance of the class</code>.</p>
<p>You can find the code to build an image in <code>lambda_function.py</code>.</p>
<pre><code class="lang-python"><span class="hljs-comment"># lambda_function.py</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    name = event[<span class="hljs-string">"name"</span>]
    message = <span class="hljs-string">f"Hello, <span class="hljs-subst">{name}</span>!"</span>

    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">200</span>,
            <span class="hljs-string">"body"</span>: message
        }
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">400</span>,
            <span class="hljs-string">"body"</span>: {<span class="hljs-string">"error"</span>: str(e)}
        }
</code></pre>
<p>As you can see from the code above, this is a very basic Python application that expects a <code>POST</code> HTTP request, with a JSON payload that contains the key – <code>name</code> – and a corresponding value. The code then returns a greeting containing the name it has received. The application has just a single function, which also serves as the entry point to it.</p>
<p>To build a Docker image, you’ll need a Dockerfile to provide the blueprint for the image. For this specific case, the Dockerfile you’ll use is also very basic. Each line in a Dockerfile is called a <code>Directive</code>, and this provides the instruction Docker should follow when creating an image. So building a Docker image means creating a template for a container by following the instructions or directives in the Dockerfile.</p>
<pre><code class="lang-plaintext"># Dockerfile

FROM public.ecr.aws/lambda/python:3.12

# Copy function code... LAMBDA_TASK_ROOT is /var/task, the working directory set in the base image
COPY lambda_function.py ${LAMBDA_TASK_ROOT}    

# Set the CMD to your handler - lambda_handler
CMD ["lambda_function.lambda_handler"]
</code></pre>
<p>A Dockerfile usually starts with a base image. To deploy an application as a Docker container in AWS Lambda, the base image has to be of a specific kind, depending on the application run-time. For this case, you’ll need the Python run-time, so the base image is <code>public.ecr.aws/lambda/python:3.12</code>. It’s okay to use a different Python version.</p>
<p>The next directive in the Dockerfile is copying the <code>lambda_function.py</code> file to a specific path in the base image. That path is referenced using an environment variable that has already been defined in the base image and points to <code>/var/task</code>. This is the directory your code will be running from.</p>
<p>The last directive is simply a command to start the application when the container runs.</p>
<p>Now, you can run the build command from the project’s root directory:</p>
<pre><code class="lang-bash">docker build -t &lt;IMAGE_NAME&gt;:&lt;iIMAGE_TAG&gt; .
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766415846066/f128b7fc-f3a0-4770-b361-3f27c36a6ec4.png" alt="Running docker build command on the terminal" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766415895836/d4653144-51b2-437d-8d73-4aaa42651206.png" alt="Output of docker images command showing a list of all existing images" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<h3 id="heading-run-the-docker-container">Run the Docker Container</h3>
<p>Next, let’s create a running container from this image.</p>
<pre><code class="lang-bash">docker run -it --rm -p 8080:8080  lambda_docker:1.0.0
</code></pre>
<p>The command above will create a container and run it in interactive mode just so you can see the logs generated by the application in the container. Port 8080 is also exposed on the host where the container is running and mapped to the container port, which is also 8080 (defined by AWS). The container gets automatically removed once you kill the running process with CTRL + C.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766416250857/62584a3c-bf5e-4cd9-b8d5-fc6734c50075.png" alt="Showing docker run command in interactive mode" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<h3 id="heading-test-the-running-container">Test the Running Container</h3>
<p>Now confirm that the application running within the container can receive and process requests. To do this, use the code in the <code>test.py</code> file:</p>
<pre><code class="lang-python"><span class="hljs-comment"># test.py</span>

<span class="hljs-keyword">import</span> requests

url = <span class="hljs-string">"http://localhost:8080/2015-03-31/functions/function/invocations"</span>

data = {
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Janet"</span>
}

response = requests.post(url, json=data)

print(<span class="hljs-string">"Status Code:"</span>, response.status_code)
print(<span class="hljs-string">"Response Body:"</span>, response.json())
</code></pre>
<p>You can use the Python <code>requests</code> library to make this call. Install the library by using a virtual environment to isolate the application from your overall system. This helps prevent issues with conflicts in the versions of libraries you install for an application to use.</p>
<p>If you’re using uv to manage your virtual environment, simply run the command:</p>
<pre><code class="lang-python">uv add requests
</code></pre>
<p>Then run the code in <code>test.py</code> from within the virtual environment:</p>
<pre><code class="lang-python">uv run python3 test.py
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766419713310/1ebc3435-3826-46fb-93f3-4218c367e280.png" alt="Testing that the running docker container is working by running test.py file" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<p>You should see the desired response on the terminal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766419866358/8f0c2867-64c6-4b16-a5a7-5a0eedf9470f.png" alt="Docker container logs in real time" class="image--center mx-auto" width="3710" height="891" loading="lazy"></p>
<h2 id="heading-how-to-push-your-image-to-amazon-elastic-container-registry-ecr">How to Push Your Image to Amazon Elastic Container Registry (ECR)</h2>
<p>Now that you have a working Docker image to deploy to Lambda, the next step is to push the image to a Docker registry. For this use case, your image has to be pushed to Amazon ECR, a container registry for storing Docker images.</p>
<p>To push your Docker image, you first need to tag the image, which simply means naming the image in a specific way.</p>
<p>Currently, this image tag is <code>lambda-docker:1.0.0</code>. To tag it the AWS way, first create an ECR repository. Let’s use the AWS CLI for this (this requires you to configure the AWS credentials locally by running the <code>aws configure</code> command and providing your credentials).</p>
<h3 id="heading-setup-environment-variables">Setup Environment Variables</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Set AWS profile</span>
<span class="hljs-built_in">export</span> AWS_PROFILE=&lt;PROFILE_NAME&gt;
</code></pre>
<pre><code class="lang-bash"><span class="hljs-comment"># Set other variables</span>

AWS_REGION=&lt;AWS_REGION&gt;
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REPO_NAME=lambda-docker
TAG=1.0.0
</code></pre>
<p>The above commands set the <code>AWS_PROFILE</code> for the CLI to target the right AWS account for API calls. The other variables specify the region, account ID, and the ECR repository name and tag.</p>
<h3 id="heading-create-ecr-repository-and-authenticate">Create ECR Repository and Authenticate</h3>
<p>Now, create the ECR repository:</p>
<pre><code class="lang-bash">aws ecr create-repository \
  --repository-name <span class="hljs-string">"<span class="hljs-variable">$REPO_NAME</span>"</span> \
  --region <span class="hljs-string">"<span class="hljs-variable">$AWS_REGION</span>"</span>
</code></pre>
<p>Authenticate to Amazon ECR:</p>
<pre><code class="lang-bash">aws ecr get-login-password --region <span class="hljs-string">"<span class="hljs-variable">$AWS_REGION</span>"</span> \
  | docker login \
  --username AWS \
  --password-stdin <span class="hljs-string">"<span class="hljs-variable">$ACCOUNT_ID</span>.dkr.ecr.<span class="hljs-variable">$AWS_REGION</span>.amazonaws.com"</span>
</code></pre>
<h3 id="heading-tag-and-push-the-docker-image">Tag and Push the Docker Image</h3>
<p>Now, tag the Docker image:</p>
<pre><code class="lang-bash">docker tag <span class="hljs-variable">$REPO_NAME</span>:<span class="hljs-variable">$TAG</span> \
  <span class="hljs-variable">$ACCOUNT_ID</span>.dkr.ecr.<span class="hljs-variable">$AWS_REGION</span>.amazonaws.com/<span class="hljs-variable">$REPO_NAME</span>:<span class="hljs-variable">$TAG</span>
</code></pre>
<p>Push the image to the ECR repository you created:</p>
<pre><code class="lang-bash">docker push <span class="hljs-variable">$ACCOUNT_ID</span>.dkr.ecr.<span class="hljs-variable">$AWS_REGION</span>.amazonaws.com/<span class="hljs-variable">$REPO_NAME</span>:<span class="hljs-variable">$TAG</span>
</code></pre>
<p>And that’s it! Your image is now in ECR.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766420761622/5a18e41b-be41-4660-8d6c-59b12aebb4de.jpeg" alt="Image of Amazon ECR showing the repository created earlier" class="image--center mx-auto" width="1920" height="1037" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766420810814/9f65af4b-a509-45e3-be8f-0bed08cfe6b2.png" alt="Image of the docker image pushed to the existing ECR repository" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<h2 id="heading-how-to-deploy-your-docker-image-to-lambda">How to Deploy Your Docker Image to Lambda</h2>
<p>With your image now in ECR, you can create a Lambda function. Navigate to the Lambda console, and click <code>Create a Function</code>.</p>
<h3 id="heading-create-lambda-function">Create Lambda Function</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421062231/19bae74d-a6d5-4e73-8cca-102be40be214.png" alt="AWS Lambda Console" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p>Select <code>Container Image</code> and go ahead to search for the ECR repository you created.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421207358/25ae6eb2-1b1b-43c7-86dc-6dcd512ddc81.jpeg" alt="Select ECR repository to create a Lambda function" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p>Next, select the image:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421335963/ab7d9103-0ea6-4e25-be8c-139344acb5c5.png" alt="Select the existing Docker image from ECR" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p>Leave other configurations as default and click create.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421506518/2f6e631a-a0c7-4f20-966f-2ef87f91bfb7.jpeg" alt="Hit the Create button to create a Lambda function" class="image--center mx-auto" width="1920" height="1033" loading="lazy"></p>
<p>Navigate to the function after creating.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421673261/71c60ac4-35e7-4458-b4a7-1be2440b9e16.jpeg" alt="The newly created Lambda function dashboard/ overview" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<h3 id="heading-test-deployment">Test Deployment</h3>
<p>Now, let’s test the deployment. For this, simply use the existing Lambda <code>Test</code> tab. Provide all the details needed, including the payload for your <code>POST</code> request.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421769909/008473e4-bb28-4fdd-8c5b-7e1f3489a3a0.png" alt="Create a new test instance to test the Lambda function" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1766421889043/86f6dbe6-be94-4dca-973e-9e7b68064ff3.png" alt="The output of testing Lambda function" class="image--center mx-auto" width="3710" height="1996" loading="lazy"></p>
<p>And that’s it. You’ve successfully deployed a Docker container on AWS by leveraging ECR and Lambda. You can go a step forward by integrating API Gateway and making the function accessible from the internet.</p>
<h2 id="heading-cleanup">Cleanup</h2>
<p>Remember to delete the services you’ve created on your AWS ECR repository and Lambda to avoid extra charges.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Deploying your Docker container on AWS Lambda is an efficient way to get your application running quickly without being bothered by managing servers or platforms.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Run a Postgres Database in Azure Kubernetes Service and Integrate it with a Node.js Express Application ]]>
                </title>
                <description>
                    <![CDATA[ Hey everyone! Today, you're going to learn about deploying a Postgres container in Azure Kubernetes Service (AKS) and connecting it to a Node.js application. In this fast-paced development landscape, deploying via containers, particularly with Kubern... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-run-postgres-in-kubernetes/</link>
                <guid isPermaLink="false">66d45dd851f567b42d9f8435</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express JS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayomide Wilfred Adeyemi ]]>
                </dc:creator>
                <pubDate>Wed, 08 May 2024 20:43:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/Azure-K8s-article-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hey everyone! Today, you're going to learn about deploying a Postgres container in Azure Kubernetes Service (AKS) and connecting it to a Node.js application.</p>
<p>In this fast-paced development landscape, deploying via containers, particularly with Kubernetes, is becoming increasingly popular. Some companies perform numerous deployments daily, so it's crucial for you to learn these technologies.</p>
<p>Kubernetes is a popular choice to deploy containerized applications like web servers, databases, and APIs. You can set up Kubernetes either locally or in the cloud. In this tutorial, we'll explore setting up Kubernetes on a cloud platform, specifically Azure.</p>
<p>I'll walk you through the process of setting up Kubernetes using Azure Kubernetes Service (AKS). You'll configure your YAML file using StatefulSet, Persistent Volume, and Services to deploy a PostgreSQL database on Azure Kubernetes. Then, you'll obtain the PostgreSQL database credentials running inside the AKS and use them to establish a connection with a Node.js application.</p>
<p>We'll cover key concepts such as deployment, stateful sets, persistent volumes, and services, preparing you to deploy a Postgres container effectively on AKS. I'll also help you connect your Node.js Express app to the Postgres container within the AKS cluster.</p>
<p>So find a comfortable seat and get ready, as we're about to dive in.</p>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Before you begin, it's important to understand some basic concepts in <a target="_blank" href="https://kubernetes.io">Kubernetes</a> like <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/pods/">pods</a>, <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">deployments</a>, <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/service/">services</a>, and <a target="_blank" href="https://kubernetes.io/docs/concepts/architecture/nodes/">nodes</a>.</p>
<p>If you're new to this, I recommend checking out the Stashchuk freeCodeCamp <a target="_blank" href="https://www.youtube.com/watch?v=d6WC5n9G_sM">video</a> for a beginner-friendly tutorial.</p>
<p>You'll also need an active <a target="_blank" href="https://azure.microsoft.com/en-us/get-started/azure-portal">Azure</a> account and subscription to follow along.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#challenges-were-trying-to-solve">Challenges We're Trying to Solve</a><br>  – <a class="post-section-overview" href="#heading-deployments">Deployments</a><br>  – <a class="post-section-overview" href="#heading-statefulsets">StatefulSets</a><br>  – <a class="post-section-overview" href="#heading-persistent-volumes">Persistent Volumes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-azure-kubernetes-service-aks">Azure Kubernetes Service (AKS)</a><br>  – <a class="post-section-overview" href="#heading-step-1-sign-in-to-your-azure-portal">Sign in to Your Azure Portal</a><br>  – <a class="post-section-overview" href="#heading-step-2-create-a-resource">Create a Resource</a><br>  – <a class="post-section-overview" href="#heading-step-3-create-a-new-container">Create a new container</a><br>  – <a class="post-section-overview" href="#heading-step-4-create-a-new-azure-kubernetes-service-aks">Create a new Azure Kubernetes Service(AKS)</a><br>  – <a class="post-section-overview" href="#heading-step-5-create-a-new-resource-group">Create a new resource group</a><br>  – <a class="post-section-overview" href="#heading-step-6-give-your-kubernetes-cluster-a-name">Give your Kubernetes cluster a name</a><br>  – <a class="post-section-overview" href="#heading-step-7-navigate-to-the-node-pool-page">Navigate to the node pool page</a><br>  – <a class="post-section-overview" href="#heading-step-8-enable-container-logs-and-set-up-alerts">Enable container logs and set up alerts</a><br>  – <a class="post-section-overview" href="#heading-step-9-advanced-section">Advanced Section</a><br>  – <a class="post-section-overview" href="#heading-step-10-tags">Tags</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-connect-to-your-aks-cluster-using-the-command-line">Connect to Your AKS Cluster</a><br>  – <a class="post-section-overview" href="#heading-download-azure-cli-and-kubectl">Download Azure CLI and kubectl</a><br>  – <a class="post-section-overview" href="#heading-verify-if-the-azure-cli-is-installed-by-typing-the-command-az-version">Verify if Azure CLI is installed</a><br>  – <a class="post-section-overview" href="#heading-verify-if-kubectl-is-installed">Verify if kubectl is installed</a><br>  – <a class="post-section-overview" href="#heading-login-to-your-azure-account">Login to Azure account</a><br>  – <a class="post-section-overview" href="#heading-configure-kubectl-to-connect-to-your-azure-kubernetes">Configure kubectl</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-resources-with-yaml">How to Create Resources with YAML</a><br>  – <a class="post-section-overview" href="#heading-clone-the-repository">Clone the Repository</a><br>  – <a class="post-section-overview" href="#heading-open-the-cloned-repository-in-any-text-editor">Open the Repository</a><br>  – <a class="post-section-overview" href="#heading-install-project-dependencies">Install Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-yaml-configuration">YAML Configuration</a><br>  – <a class="post-section-overview" href="#heading-storageclass">StorageClass</a><br>  – <a class="post-section-overview" href="#heading-persistentvolumeclaim">PersistentVolumeClaim</a><br>  – <a class="post-section-overview" href="#heading-configmap">ConfigMap</a><br>  – <a class="post-section-overview" href="#heading-statefulset">StatefulSet</a><br>  – <a class="post-section-overview" href="#heading-service">Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-deploy-yaml-resource-to-azure-kubernetes-service-aks">How to Deploy YAML Resource to Azure</a><br>  – <a target="_blank" href="https://www.freecodecamp.org/news/p/a37cba54-1e70-4fb6-99d4-d9ee63e66e1b/deploy-the-yaml-resource">Deploy the YAML resource</a></p>
</li>
<li><p><a class="post-section-overview" href="#nodejs-application">Node.js Application</a><br>  – <a class="post-section-overview" href="#heading-configure-your-nodejs-application">Configure Nodejs</a><br>  – <a class="post-section-overview" href="#heading-run-your-nodejs-application">Run Nodejs Application</a><br>  – <a class="post-section-overview" href="#heading-test-the-application">Test the Application</a><br>  – <a class="post-section-overview" href="#heading-open-your-postman-application">Open Postman</a><br>  – <a class="post-section-overview" href="#heading-confirm-the-data">Confirm the Data</a><br>  – <a class="post-section-overview" href="#heading-delete-the-pod-to-confirm-data-persistence">Delete Pod</a><br>  – <a class="post-section-overview" href="#heading-data-persistence">Data Persistence</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-challenges-were-trying-to-solve">Challenges We're Trying to Solve</h2>
<p>Firstly, what is Kubernetes? Well, it's like a manager for your software containers. It helps you run and manage lots of containers like web servers, databases, microservices, and APIs which are like little packages holding your applications.</p>
<p>Kubernetes takes care of things like starting, stopping, and scaling these containers, so your apps run smoothly even when there is more load on your application. It's popular because it makes running software in the cloud easier and more reliable.</p>
<p>Now, let's talk about how to tackle some challenges you might face with a real-world application running Postgres in a Kubernetes production cluster.</p>
<p>Imagine that the infrastructure hosting your Postgres crashes, causing you to lose all the services and data stored in the database. Or, picture a scenario where the Postgres database becomes corrupted, leading to data loss.</p>
<p>In both cases, you need a way to back up your application so you can restore it to a working state if disaster strikes.</p>
<p>So, how do you capture a comprehensive application backup that includes all the necessary data? This backup should allow you to restore the entire application, including the database, if you lose your cluster or encounter data loss.</p>
<p>In Kubernetes, think of a Pod as the tiniest unit that you can deploy. It's like a small box that holds one thing, like a web server or a database. So, if your Pod isn't running, your web server or database isn't either.</p>
<p>This means that if the cluster where your Pod runs gets destroyed, all the data in the Pod disappears too. All the nodes (virtual machines that run your application over the network) will also be wiped out.</p>
<p>How can you make a pod stay on one specific node where the data is and never move? And how can you make sure that each pod can be found separately when you're using a load balancer?</p>
<p>One solution is to consider how you deploy your application on Kubernetes. Typically, you create a <strong>deployment</strong> and expose it using a service, specifying the service type as either Cluster type, NodePort, or LoadBalancer.</p>
<p>But not all applications are the same when it comes to state. Some applications, known as stateless applications, don't rely on storing data locally, so losing their state isn't a big issue.</p>
<p>But for applications like databases or caches, maintaining state is crucial because they rely on storage. In Kubernetes, deploying stateful applications like databases using just deployment isn't ideal. You need a solution that ensures your application's data is safely stored and can be recovered in case of failure.</p>
<h3 id="heading-deploymentshttpskubernetesiodocsconceptsworkloadscontrollersdeployment"><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">Deployments</a></h3>
<p>You might be wondering why we can't just use a Kubernetes deployment to deploy Postgres in the Kubernetes cluster? Well, the thing is, many people aren't aware of the difference between a deployment and a stateful set.</p>
<p>Let's imagine you have a pod running in your cluster that you created using a deployment. Then you scaled up to two pods, so you now have Pod A and Pod B.</p>
<p>The problem arises because, by default, pods created as part of the same deployment share the same <strong>persistent volume</strong> (PV) across the cluster. So, when you scaled up, both instances of Postgres would write to the same storage, which could lead to data corruption.</p>
<p>Another issue arises from a networking perspective. Pods A and B don't have a dependable way to communicate with each other over the network. By default, Kubernetes pods don't have their own DNS names. Instead, you rely on <strong>services</strong> to expose ports to other applications in the cluster.</p>
<p>If you take a closer look at pod names, you'll notice that pods are assigned a random hash at the end of their names. Because of this, pods lack a consistent network identity. Every time a pod is destroyed and recreated, it receives a new randomized name. This inconsistency isn't ideal for reliable networking.</p>
<p>Postgres isn't naturally made for <strong>Kubernetes</strong>, and Kubernetes can be tough when handling stateful tasks. To set up a Postgres instance, you've got to know the right Kubernetes setup. You can't just throw it in a pod, because if the pod goes down, so does your data. But, for a quick integration, a pod could work fine.</p>
<p>Deployments aren't ideal either, since you don't want your pod randomly placed on a node. But for testing, deployments are handy if you just need a Postgres instance to run temporarily.</p>
<p>What you really want is a pod that sticks to a particular node where your data resides, and stays put. Plus, you also want your pod to be individually addressable. for this we need what we called a <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/"><strong>statefulSet</strong></a>.</p>
<h3 id="heading-statefulsetshttpskubernetesiodocsconceptsworkloadscontrollersstatefulset"><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/"><strong>StatefulSets</strong></a></h3>
<p>When you update your deployment to become a <strong>StatefulSet</strong>, Kubernetes introduces some improvements for deploying stateful workloads. One major change is how it handles scaling.</p>
<p>If you specify that you want three replicas of your StatefulSet, Kubernetes won't create all three pods at once. Instead, it creates them one by one. Each pod gets its own unique DNS name, starting with the pod's name followed by an ordinal number starting from zero. So, when you scale up, the ordinal number increases for each new pod.</p>
<p>Here's the cool part: if a pod like Pod-0 is destroyed and needs to be remade, it will return with the same name. This means each pod has a specific address, even if it's replaced.</p>
<p>And here's another cool feature: each pod in a StatefulSet gets its own persistent volume (PV). This lets you keep the same storage even if you scale up or down. This brings us to another concept called persistent volumes.</p>
<h3 id="heading-persistent-volumeshttpskubernetesiodocsconceptsstoragepersistent-volumes"><a target="_blank" href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">Persistent Volumes</a></h3>
<p>Let's forget about pods, deployment, and containers for a moment. What exactly is "state"? In simple terms, state is the data that your applications need to work properly.</p>
<p>Now, when we talk about processes, there are two types: stateless and stateful. <strong>Stateless</strong> processes don't rely on any data to work. They just do their thing without needing any specific information. On the other hand, <strong>stateful</strong> processes need data or state to function properly.</p>
<p>Now, where do you store this state? There are two main places: memory and disk. <strong>Memory</strong> allows for quick access to data, which is great for applications like Redis, MongoDB, Postgres, or MySQL. They store their state on memory for quick access. But for persistent, they store it on <strong>disk</strong> on the file system (for more permanent storage).</p>
<p>Why the file system? Because it's the only way to keep the state persistent even when the system reboots. So, when a process dies and gets recreated, it can read its state from the file system.</p>
<p>I like breaking things down because I used to teach tech stuff. Now, let's get into setting up Kubernetes in Azure.</p>
<h2 id="heading-azure-kubernetes-service-aks"><strong>Azure Kubernetes Service (AKS)</strong></h2>
<p>In this section, I'll guide you through setting up a Kubernetes cluster on Azure.</p>
<h3 id="heading-step-1-sign-in-to-your-azure-portal">Step 1: Sign in to your Azure portal</h3>
<p>To begin, you will have to sign into your <a target="_blank" href="https://azure.microsoft.com/en-us/get-started/azure-portal">Azure</a> portal. Once logged in, you should see a dashboard similar to this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.39.46.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Azure portal homepage</em></p>
<h3 id="heading-step-2-create-a-resource">Step 2: Create a resource</h3>
<p>Click on "create a resource" to create a resource.</p>
<p>Resources are the various services, components, and assets that you can create and manage within the Azure cloud platform. These resources can include virtual machines, databases, storage accounts, networking components, web applications, and more.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.39.46--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating a resource in Azure portal</em></p>
<h3 id="heading-step-3-create-a-new-container">Step 3: Create a new container</h3>
<p>Next, navigate to the "Containers" category from the options available on the left pane. Click on Containers as shown by the arrow in the screenshot.</p>
<p>Again, Kubernetes is a container orchestration platform. It manages and orchestrates the deployment, scaling, and operation of application containers across clusters of machines. Kubernetes provides a framework for automating the deployment, scaling, and management of containerized applications.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.42.12--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating new container (Kubernetes) in Azure</em></p>
<h3 id="heading-step-4-create-a-new-azure-kubernetes-service-aks"><strong>Step 4: Create a new Azure Kubernetes Service (AKS)</strong></h3>
<p>Select "Azure Kubernetes Service (AKS)" from the list of available container services and click Create. This will take you to the AKS creation page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.42.37--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating a new Azure Kubernetes service</em></p>
<h3 id="heading-step-5-create-a-new-resource-group">Step 5: Create a new resource group</h3>
<p>In the "Resource group" section, click on "Create new" to create a new resource group for your Azure Kubernetes Service (AKS) deployment.</p>
<p>In Azure, a "resource group" is a logical container used to group together related Azure resources. It serves as a way to organize and manage these resources collectively, rather than individually.</p>
<p>When you create resources such as virtual machines, databases, storage accounts, or any other Azure service, you typically associate them with a resource group.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.43.09--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating a new resource group in azure portal</em></p>
<p>Let's name the resource group "AZURE-POSTGRES-RG" as shown below. You can name it anything you like. Then click ok.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.43.45.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Inputting name for the resource group</em></p>
<h3 id="heading-step-6-give-your-kubernetes-cluster-a-name">Step 6: Give your Kubernetes cluster a name</h3>
<p>Now let's name the session for configuring the Kubernetes cluster "Kubernetes Cluster Name".</p>
<p>In Azure, a Kubernetes cluster is a managed container orchestration service provided by Azure Kubernetes Service (AKS). It allows you to deploy, manage, and scale containerized applications using Kubernetes without having to manage the underlying infrastructure.</p>
<p>Give it a name like "AZURE-POSTGRES-KC" and and select a region that's close to you. In my case I select (Asia Pacific) East Asia and click next.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.47.34--3-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Naming the Kubernetes cluster name</em></p>
<h3 id="heading-step-7-navigate-to-the-node-pool-page">Step 7: Navigate to the node pool page</h3>
<p>Now it's time to configure the node pool session by clicking on the agentpool.</p>
<p>In Azure, a node pool is a group of virtual machines (VMs) that are provisioned and managed together within an Azure Kubernetes Service (AKS) cluster. Each node pool runs a specific version of Kubernetes and has its own set of configurations, such as VM size, OS image, and node count.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.47.50--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Editing agentpool</em></p>
<p>Set the minimum node count to 1, maximum node count to 2, and the maximum pods per node to 30 to minimise cost. Then click update.</p>
<p>These parameters help control the size and behavior of the node pool in an Azure Kubernetes Service (AKS) cluster:</p>
<ol>
<li><p><strong>Minimum Node Count</strong>: Ensures a minimum number of nodes are always available for consistent performance and availability, even during low-demand periods.</p>
</li>
<li><p><strong>Maximum Node Count</strong>: Sets an upper limit on the number of nodes in the node pool to manage costs and prevent over-provisioning.</p>
</li>
<li><p><strong>Maximum Pods per Node</strong>: Defines the maximum number of pods that can run on each node, optimizing resource utilization and preventing overcrowding.</p>
</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.48.29--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Updating agentpool details</em></p>
<p>Once you've clicked "Update," you'll be directed to the "Networking" section as shown below. Keep the page as is and proceed by clicking "Next." This will take you to Integration session.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.48.55--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Navigating to Next Page</em></p>
<p>Azure Container Registry (ACR) is a fully managed private Docker registry service provided by Microsoft Azure. It enables developers to store, manage, and deploy Docker container images securely within their Azure environment.</p>
<p>You will need a place to store the Docker image that's pulled.</p>
<p>To begin, select "Create New" to set up a new container registry. This action will bring up a page where you can input the necessary details, as illustrated on the right side of the image below. Enter the details as indicated by the arrows and then click "Okay." Once you're done, proceed by clicking "Next."</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.49.36--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Naming and editing Azure Container Registry details</em></p>
<h3 id="heading-step-8-enable-container-logs-and-set-up-alerts">Step 8: Enable container logs and set up alerts</h3>
<p>The <strong>Enable Container Logs</strong> option allows you to turn on logging for your containers. Logging records important information about what's happening inside your containers, like errors, warnings, and other events. It's useful for troubleshooting and monitoring your applications.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.50.25--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Choosing container logs</em></p>
<h3 id="heading-step-9-advanced-section">Step 9: Advanced section</h3>
<p>Keep the Monitoring section unchanged and proceed by clicking "Next."</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.50.32.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Navigating to Next Page</em></p>
<h3 id="heading-step-10-tags">Step 10: Tags</h3>
<p>Keep the Tags section unchanged and proceed by clicking "Next."</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.50.44.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Navigating to Next Page</em></p>
<h3 id="heading-step-11-click-review-create-to-finalize-the-deployment">Step 11: Click "Review + create" to finalize the deployment</h3>
<p>Once completed, your resource group, Azure Kubernetes Service (AKS), Azure Container Registry, and Kubernetes cluster will be created.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.51.39--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Completing Azure Kubernetes Setup</em></p>
<p>The screenshot below shows that the deployment was successful.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-18.01.53.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Successful Deployment</em></p>
<p>You've just successfully created an Azure Kubernetes Service from the Azure portal. Congrats!</p>
<h2 id="heading-how-to-connect-to-your-aks-cluster-using-the-command-line">How to Connect to Your AKS Cluster Using the Command Line</h2>
<p>After successfully creating a new AKS in the Azure portal, the next step is to establish a connection to that cluster.</p>
<p>In this section, I'll guide you through Azure login, configuring kubectl to use the current context, and creating the YAML file for our Postgres container. This file will include StatefulSet, persistent volume, persistent volume claim, config map, and using Azure File for data storage.</p>
<p>I'll also show you how to run a Node.js Express application locally, use Postman to test the endpoints, and receive a response confirming that data was sent to the database successfully.</p>
<h3 id="heading-download-azure-cli-and-kubectl">Download Azure CLI and kubectl</h3>
<p>To start, you'll need to download the Azure CLI and kubectl.</p>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli"><strong>Azure CLI</strong></a> <strong>(Command-Line Interface)</strong>: a command-line tool provided by Microsoft for managing Azure resources. It allows users to interact with Azure services and resources directly from the command line, making it easy to automate tasks, create scripts, and manage Azure resources programmatically.</p>
</li>
<li><p><a target="_blank" href="https://kubernetes.io/docs/tasks/tools/"><strong>kubectl</strong></a>: a command-line tool for managing Kubernetes clusters, used to deploy, scale, and manage containerized applications. It allows users to perform operations like deploying applications, managing pods, services, and deployments, inspecting cluster resources, scaling applications, and debugging issues, simplifying management of containerized workloads in a Kubernetes environment.</p>
</li>
</ul>
<p>I'm using the warp terminal. <a target="_blank" href="https://www.warp.dev/">Warp</a> is the terminal reimagined with AI and collaborative tools for better productivity. You can run the command using PowerShell on Windows or Terminal on Mac. I'm using a MacBook.</p>
<h3 id="heading-verify-if-the-azure-cli-is-installed-by-typing-the-command-az-version">Verify if the Azure CLI is installed by typing the command <code>az --version</code></h3>
<p>Once the download finishes, verify whether Azure CLI is installed on your computer by running the command <code>az --version</code>. If the installation is successful, you should see an output similar to this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-21.18.48.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Verifying Azure CLI Installation</em></p>
<h3 id="heading-verify-if-kubectl-is-installed">Verify if kubectl is installed</h3>
<p>To check if kubectl is installed, just type <code>kubectl version</code> in the command line.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-21.31.01.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Verifying Kubectl Installation</em></p>
<h3 id="heading-login-to-your-azure-account">Login to your Azure account</h3>
<p>Enter <code>az login</code> in the command line. This will open your browser and prompt you to sign in to your Azure account.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-21.47.47.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Logging into Azure</em></p>
<p>After signing in, it shows details about your Azure subscription, including the subscription name, ID, and user information.</p>
<h3 id="heading-select-an-azure-subscription">Select an Azure subscription</h3>
<p>Azure subscriptions are logical containers used to provision resources in Azure. You'll need to locate the subscription ID that you plan to use in this module. Use the command to list your Azure subscriptions:</p>
<pre><code class="lang-bash">az account list --output table
</code></pre>
<p>Use the following command to ensure you're using an Azure subscription that allows you to create resources for the purpose of this module, substituting your subscription ID (SubscriptionId):</p>
<pre><code class="lang-bash">az account <span class="hljs-built_in">set</span> --subscription <span class="hljs-string">"Name of the subscription"</span>
</code></pre>
<h3 id="heading-configure-kubectl-to-connect-to-your-azure-kubernetes">Configure kubectl to connect to your Azure Kubernetes</h3>
<p>Replace <code>Your_Azure_Resource_groups_name</code> in the code below with the name you chose when creating a resource group. Also, replace <code>your_azure_kubernetes_service_name</code> with the name of your Kubernetes cluster. Then, execute the following command:</p>
<pre><code class="lang-bash">az aks get-credentials --resource-group [Your_Azure_Resource_groups_name] --name [your_azure_kubernetes_service_name]
</code></pre>
<p>The output should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.07.20.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Merging kubectl with Azure Kubernetes Service</em></p>
<h3 id="heading-verify-if-kubectl-has-been-merged-successfully">Verify if kubectl has been merged successfully</h3>
<p>Run the following command <code>kubectl get nodes</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.09.33.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Verifying if merged is successful</em></p>
<p>When you run this command, Kubernetes communicates with the cluster's control plane to fetch a list of all the nodes that are part of the cluster you created. As you can see, this is the node that was running in the Kubernetes cluster we created inside Azure.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-18.04.07.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Virtual Node running in AKS cluster</em></p>
<h3 id="heading-run-the-command-kubectl-get-pods">Run the command <code>kubectl get pods</code></h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.18.19.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Displaying Pod Information</em></p>
<p>When you run the command <code>kubectl get pods</code>, Kubernetes attempts to retrieve information about all pods within the default namespace of your cluster. But in this case, the output indicates that there are no resources (pods) found within the default namespace, implying that no pods currently exist in that namespace.</p>
<p>A <strong>namespace</strong> in Kubernetes is a virtual cluster environment within which resources like pods, services, and deployments are organized and isolated. It's a way to divide cluster resources between multiple users, teams, or projects. Namespaces provide a scope for names and make it easier to manage and control access to resources.</p>
<p>By default, Kubernetes starts with a "default" namespace, but you can create additional namespaces to organize and manage resources more effectively. Namespaces help prevent naming conflicts and provide a logical separation of resources, allowing different teams or projects to work independently within the same Kubernetes cluster.</p>
<h3 id="heading-create-a-namespace">Create a namespace</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.24.40.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating Namespace</em></p>
<p>When you run the command <code>kubectl create namespace database</code>, Kubernetes creates a new namespace named "database." The output "namespace/database created" confirms that the namespace has been successfully created.</p>
<p>You can now use this namespace to organize and manage resources related to databases within the Kubernetes cluster.</p>
<h3 id="heading-confirm-the-namespace">Confirm the namespace</h3>
<p>The command <code>kubectl get namespace</code> lists all namespaces in the Kubernetes cluster including the database namespace we just created, showing their names, status (active), and age.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.26.22.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Confirming namespace</em></p>
<h3 id="heading-get-pod-information-in-database-namespace">Get pod information in database namespace</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.35.29.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Displaying Pod Information related to database namespace</em></p>
<p>This command, <code>kubectl get pods -n database</code>, attempts to fetch information about pods specifically within the "database" namespace. But the output <code>No resources found in database namespace</code> indicates that there are currently no pods deployed in the "database" namespace.</p>
<h2 id="heading-how-to-create-resources-with-yaml">How to Create Resources with YAML</h2>
<p>Let's explore creating resources with YAML to provision our PostgreSQL database running in an Azure Kubernetes cluster. But first, what exactly is YAML?</p>
<p>Kubernetes <a target="_blank" href="https://www.redhat.com/en/topics/automation/what-is-yaml"><strong>YAML</strong></a> is a configuration file written in YAML (YAML Ain't Markup Language). They define how Kubernetes resources like pods, deployments, and services should be set up within a cluster. These files are easy to read and specify details like resource names, types, specifications, labels, and annotations. They're crucial for deploying applications and infrastructure on Kubernetes clusters.</p>
<p>YAML is what you will use to create Kubernetes resources that will run Postgres.</p>
<p>First, you need to <a target="_blank" href="https://github.com/ayowilfred95/Azure-k8s-postgres.git">clone this GitHub repository</a>. Inside, you'll find a Node.js Express application and a YAML file. The Node.js app allows users to register with their email, password, and full name, and also enables them to log in by verifying their details in the database. If their details are found, it displays a success message.</p>
<h3 id="heading-clone-the-repository">Clone the repository</h3>
<p>Create a new folder on your computer and then clone this <a target="_blank" href="https://github.com/ayowilfred95/Azure-k8s-postgres.git">repository</a> into it.</p>
<p>Open your terminal or PowerShell, go to the folder you want, and use the command below to clone the repository into your computer in that location.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/ayowilfred95/Azure-k8s-postgres.git
</code></pre>
<h3 id="heading-open-the-cloned-repository-in-any-text-editor">Open the cloned repository in any text editor</h3>
<p>I'm using Visual Studio Code, but feel free to use any text editor you prefer. Here's the structure of the project:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-04.15.29.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Project folder structure</em></p>
<h3 id="heading-install-project-dependencies">Install project dependencies</h3>
<p>Open the terminal in VS Code and go to the main directory of the project. Next, execute the command <code>npm install</code> to install all the required packages and dependencies for the project:</p>
<pre><code class="lang-bash">npm install
</code></pre>
<p>Since the backend application is a Node.js Express app, you use npm to install dependencies (similar to how we use <code>maven clean install</code> in Java).</p>
<p>After the dependencies are installed, open the file named "postgres.yaml". It holds all the YAML configurations required to set up your PostgreSQL database that will run in the Kubernetes cluster.</p>
<h2 id="heading-yaml-configuration">YAML Configuration</h2>
<p>In the postgres.yaml file, there are five configurations separated by ---. It's important to use this "---" symbol when declaring different types of Kubernetes resources. If you forget to do this, you'll encounter an error.</p>
<h3 id="heading-storageclass">StorageClass</h3>
<p>The first one is the <code>StorageClass</code>. This YAML configuration defines a StorageClass in Kubernetes for managing storage resources.</p>
<pre><code class="lang-bash">kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: azuredisk-premium-retain
provisioner: kubernetes.io/azure-disk
reclaimPolicy: Retain   <span class="hljs-comment"># Retain or Delete</span>
volumeBindingMode: WaitForFirstConsumer   <span class="hljs-comment"># WaitForFirstConsumer or Immediate</span>
allowVolumeExpansion: <span class="hljs-literal">true</span>    <span class="hljs-comment"># true or false</span>
parameters:
  storageaccounttype: Premium_LRS   <span class="hljs-comment"># Premium or Standard</span>
  kind: Managed
</code></pre>
<p>Let's break down what each part means:</p>
<ul>
<li><p><code>kind: StorageClass</code>: Indicates the type of Kubernetes resource being defined, which is a <code>StorageClass</code>. A <code>StorageClass</code> defines the class of storage offered by a cluster.</p>
</li>
<li><p><code>apiVersion: storage.k8s.io/v1</code>: Specifies the Kubernetes API version being used for this resource.</p>
</li>
<li><p><code>metadata: name: azuredisk-premium-retain</code>: Provides metadata for the <code>StorageClass</code>, including its name, which in this case is "azuredisk-premium-retain".</p>
</li>
<li><p><code>provisioner: kubernetes.io/azure-disk</code>: Specifies the provisioner responsible for provisioning storage. In this case, it's "kubernetes.io/azure-disk", indicating that Azure Disk will be used as the storage provisioner.</p>
</li>
<li><p><code>reclaimPolicy: Retain</code>: Defines the reclaim policy for the storage resources. It specifies what action should be taken when the associated persistent volume is released. Here, it's set to "Retain", meaning the volume is retained even after it's no longer used by a pod.</p>
</li>
<li><p><code>volumeBindingMode: WaitForFirstConsumer</code>: Specifies the volume binding mode, which determines when volume binding should occur. In this case, it's set to "WaitForFirstConsumer", meaning the volume will be bound when the first pod using it is created.</p>
</li>
<li><p><code>allowVolumeExpansion: true</code>: Indicates whether volume expansion is allowed. Setting it to "true" means that the size of the volume can be increased if needed.</p>
</li>
<li><p><code>parameters</code>: Contains additional parameters specific to the provisioner. Here, it specifies the storage account type as "Premium_LRS" and the kind of storage as "Managed".</p>
</li>
</ul>
<p>Overall, this configuration sets up a <code>StorageClass</code> named "azuredisk-premium-retain" using Azure Disk as the provisioner, with specific policies and parameters tailored for Azure storage.</p>
<h3 id="heading-persistentvolumeclaim">PersistentVolumeClaim</h3>
<p>The second configuration in the postgres.yaml file is the <strong>persistent volume claim</strong>.</p>
<p>This YAML configuration defines a <code>PersistentVolumeClaim</code> (PVC) in Kubernetes, which is used to request storage resources.</p>
<pre><code class="lang-bash">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: azure-managed-disk-pvc
spec:
  accessModes:
  - ReadWriteOnce   <span class="hljs-comment"># ReadWriteOnce, ReadOnlyMany or ReadWriteMany</span>
  storageClassName: azuredisk-premium-retain
  resources:
    requests:
      storage: 4Gi
</code></pre>
<p>Let's break down what each part means:</p>
<ul>
<li><p><code>apiVersion: v1</code>: Specifies the Kubernetes API version being used for this resource.</p>
</li>
<li><p><code>kind: PersistentVolumeClaim</code>: Indicates the type of Kubernetes resource being defined, which is a PersistentVolumeClaim. A PVC is used by pods to request storage resources.</p>
</li>
<li><p><code>metadata: name: azure-managed-disk-pvc</code>: Provides metadata for the PersistentVolumeClaim, including its name, which is "azure-managed-disk-pvc".</p>
</li>
<li><p><code>spec</code>: Describes the desired state of the PersistentVolumeClaim.</p>
</li>
<li><p><code>accessModes: - ReadWriteOnce</code>: Specifies the access mode for the volume. Here, it's set to "ReadWriteOnce", meaning the volume can be mounted as read-write by a single node at a time.</p>
</li>
<li><p><code>storageClassName: azuredisk-premium-retain</code>: Specifies the <code>StorageClass</code> to use for provisioning the volume. This PVC will use the <code>StorageClass</code> named "azuredisk-premium-retain" defined previously.</p>
</li>
<li><p><code>resources: requests: storage: 4Gi</code>: Specifies the desired storage capacity for the volume. Here, it requests 4 gigabytes (Gi) of storage.</p>
</li>
</ul>
<p>Overall, this configuration sets up a <code>PersistentVolumeClaim</code> named "azure-managed-disk-pvc" requesting storage resources with specific access modes, storage class, and storage capacity.</p>
<h3 id="heading-configmap">ConfigMap</h3>
<p>The third configuration in the postgres.yaml file is the <strong>config map</strong>. This YAML configuration defines a ConfigMap in Kubernetes, which is used to store configuration data in key-value pairs.</p>
<pre><code class="lang-bash">apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-config
  labels:
    app: postgres
data:
  POSTGRES_DB: freecodecamp
  POSTGRES_USER: freecodecamp1
  POSTGRES_PASSWORD: freecodecamp@
  PGDATA: /var/lib/postgresql/data/pgdata
</code></pre>
<p>Let's break down what each part means:</p>
<ul>
<li><p><code>apiVersion: v1</code>: Specifies the Kubernetes API version being used for this resource.</p>
</li>
<li><p><code>kind: ConfigMap</code>: Indicates the type of Kubernetes resource being defined, which is a <code>ConfigMap</code>. A <code>ConfigMap</code> is used to store non-confidential data in key-value pairs.</p>
</li>
<li><p><code>metadata: name: postgres-config</code>: Provides metadata for the <code>ConfigMap</code>, including its name, which is "postgres-config".</p>
</li>
<li><p><code>labels: app: postgres</code>: Labels are key-value pairs used to organize and select resources. Here, a label "app" with the value "postgres" is applied to the <code>ConfigMap</code>.</p>
</li>
<li><p><code>data</code>: Contains the key-value pairs of configuration data.</p>
</li>
<li><p><code>POSTGRES_DB: pisonitsha</code>: Specifies the name of the PostgreSQL database as "pisonitsha".</p>
</li>
<li><p><code>POSTGRES_USER: pisonitsha1</code>: Specifies the username for accessing the PostgreSQL database as "pisonitsha1".</p>
</li>
<li><p><code>POSTGRES_PASSWORD: pisonitsha@</code>: Specifies the password for accessing the PostgreSQL database as "pisonitsha@".</p>
</li>
<li><p><code>PGDATA: /var/lib/postgresql/data/pgdata</code>: Specifies the location of PostgreSQL data directory as "/var/lib/postgresql/data/pgdata".</p>
</li>
</ul>
<p>Overall, this configuration sets up a ConfigMap named "postgres-config" containing key-value pairs of configuration data, such as database name, username, password, and data directory location, which can be used by other Kubernetes resources.</p>
<p><strong>Note:</strong> It's recommended to avoid hardcoding secret variables such as <code>POSTGRES_DB</code>, <code>POSTGRES_PASSWORD</code>,<code>PGDATA</code> and instead store them in secret files, for the sake of simplicity in this tutorial, we'll keep them hardcoded.</p>
<h3 id="heading-statefulset">StatefulSet</h3>
<p>The fourth configuration is the <strong>stateful set</strong>.This YAML configuration defines a <code>StatefulSet</code> in Kubernetes, which is used to manage stateful applications like databases.</p>
<pre><code class="lang-bash">apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  selector:
    matchLabels:
      app: postgres
  replicas: 1
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:10.4
          imagePullPolicy: <span class="hljs-string">"IfNotPresent"</span>
          ports:
          - containerPort: 5432
          envFrom:
          - configMapRef:
              name: postgres-config
          volumeMounts:
          - name: azure-managed-disk-pvc
            mountPath: /var/lib/postgresql/data
      volumes:
      - name: azure-managed-disk-pvc
        persistentVolumeClaim:
          claimName: azure-managed-disk-pvc
    ```

Let<span class="hljs-string">'s break down what each part means:

* `apiVersion: apps/v1`: Specifies the Kubernetes API version being used for this resource.
* `kind: StatefulSet`: Indicates the type of Kubernetes resource being defined, which is a `StatefulSet`. `StatefulSets` are used to manage stateful applications by providing unique **identities** and stable **network** identities to each pod.
* `metadata: name: postgres`: Provides metadata for the `StatefulSet`, including its name, which is "postgres".
* `spec`: Describes the desired state of the `StatefulSet`.
* `serviceName: postgres`: Specifies the name of the Kubernetes service that will be used to access the `StatefulSet` pods.
* `selector: matchLabels: app: postgres`: Selects the pods controlled by this `StatefulSet` based on the label "app: postgres".
* `replicas: 1`: Specifies the desired number of replicas (instances) of the StatefulSet, which is 1 in this case.
* `template`: Defines the pod template used to create pods managed by the `StatefulSet`.
* `metadata: labels: app: postgres`: Labels applied to the pods created from this template.
* `spec`: Describes the specification of the containers within the pod.
* `containers`: Specifies the containers running in the pod.
* `name: postgres`: Defines the name of the container as "postgres".
* `image: postgres:10.4`: Specifies the Docker image used for the container, which is "postgres:10.4".
* `imagePullPolicy: "IfNotPresent"`: Specifies the policy for pulling the container image, which is "IfNotPresent", meaning it will only pull the image if it'</span>s not already present on the node.
* `ports: containerPort: 5432`: Specifies the port that the PostgreSQL service inside the container is listening on.
* `envFrom: configMapRef: name: postgres-config`: Injects environment variables from a ConfigMap named <span class="hljs-string">"**postgres-config**"</span> that you defined earlier.
* `volumeMounts: name: azure-managed-disk-pvc mountPath: /var/lib/postgresql/data`: Mounts a persistent volume claim named <span class="hljs-string">"azure-managed-disk-pvc"</span> to the container at the specified path.
* `volumes: name: azure-managed-disk-pvc persistentVolumeClaim: claimName: azure-managed-disk-pvc`: Defines the persistent volume claim named <span class="hljs-string">"azure-managed-disk-pvc"</span> to be used by the pod.

Overall, this configuration sets up a StatefulSet named <span class="hljs-string">"postgres"</span> with one replica, running a PostgreSQL container with specific settings and mounted persistent storage.

<span class="hljs-comment">### Service</span>

The fifth configuration is the **service**. This YAML configuration defines a **Service** <span class="hljs-keyword">in</span> Kubernetes, <span class="hljs-built_in">which</span> is used to expose the `StatefulSet` we declared earlier as a network service.

```bash
apiVersion: v1
kind: Service
metadata:
  name: postgres
  labels:
    app: postgres
spec:
  <span class="hljs-built_in">type</span>: LoadBalancer
  selector:
    app: postgres
  ports:
    - protocol: TCP
      name: https
      port: 5432
      targetPort: 5432
</code></pre>
<p>Let's break down what each part means:</p>
<ul>
<li><p><code>apiVersion: v1</code>: Specifies the Kubernetes API version being used for this resource.</p>
</li>
<li><p><code>kind: Service</code>: Indicates the type of Kubernetes resource being defined, which is a Service. <strong>Services</strong> allow pods to be accessed by other pods or external users.</p>
</li>
<li><p><code>metadata: name: postgres</code>: Provides metadata for the Service, including its name, which is "postgres".</p>
</li>
<li><p><code>labels: app: postgres</code>: Labels are key-value pairs used to organize and select resources. Here, a label "app" with the value "postgres" is applied to the Service.</p>
</li>
<li><p><code>spec</code>: Describes the desired state of the Service.</p>
</li>
<li><p><code>type: LoadBalancer</code>: Specifies the type of Service, which is "LoadBalancer". This type allows the <strong>Service</strong> to be exposed externally with a cloud provider's load balancer.</p>
</li>
<li><p><code>selector: app: postgres</code>: Selects the pods controlled by the Service based on the label "app: postgres".</p>
</li>
<li><p><code>ports</code>: Specifies the ports that the Service will listen on.</p>
</li>
<li><p><code>protocol: TCP</code>: Specifies the protocol used for the port, which is TCP.</p>
</li>
<li><p><code>name:https</code> : Specifies a name for the port, which is "https".</p>
</li>
<li><p><code>port: 5432</code>: Specifies the port number on which the Service will listen, which is 5432.</p>
</li>
<li><p><code>targetPort: 5432</code>: Specifies the target port on the pods to which traffic will be forwarded, which is also 5432. This means that traffic received on port 5432 of the Service will be forwarded to port 5432 on the pods.</p>
</li>
</ul>
<p>Overall, this configuration sets up a Service named "postgres" with a LoadBalancer type, forwarding traffic on port 5432 to pods labeled with "app: postgres".</p>
<h2 id="heading-how-to-deploy-yaml-resource-to-azure-kubernetes-service-aks">How to Deploy YAML Resource to Azure Kubernetes Service (AKS)</h2>
<p>You've previously connected "kubectl" with the Azure Kubernetes Service (AKS) you set up. Let's double-check it.</p>
<p>In your VS Code terminal, rerun the command <code>kubectl get nodes</code>. You'll see an output like this, though your node's value will be different.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/Screenshot-2024-05-06-at-05.49.43.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Displaying node information running in Azure Kubernetes cluster</em></p>
<p>Next, verify the namespace you previously created by executing the command: <code>kubectl get namespace database</code>. Your output should resemble this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-05.49.12.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Retrieving Namespace Information</em></p>
<h3 id="heading-deploy-the-yaml-resource">Deploy the YAML resource</h3>
<p>Once you've confirmed everything is set, you can deploy the YAML resource. This will establish your PostgreSQL database in the Azure Kubernetes cluster you've configured.</p>
<p>Run the below command in the main directory where the configuration file is located. Currently, I'm in the project's root directory (azure-k8s-postgres). To deploy the database, just execute this command below:</p>
<pre><code class="lang-bash">kubectl apply -n database -f postgres.yaml
</code></pre>
<p>Your output should look like this. This output confirms that all these components have been successfully created in Kubernetes.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-05.57.52.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Applying Configuration to Namespace</em></p>
<p>Execute the command below to verify that the pod is running:</p>
<pre><code class="lang-bash">kubectl get pods -n database
</code></pre>
<p>Your output should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-06.01.33.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Fetching Pods in Namespace</em></p>
<p>This output confirms that a pod name "postgres-0" is running in your Azure Kubernetes Cluster. But it was not the only pod you created. As I said earlier, to connect to a pod, you need what is called service. And you have declared a service resource in our configuration file which has also been deployed into your Kubernetes.</p>
<p>To get the status of the service, run this command:</p>
<pre><code class="lang-bash">kubectl get services -n database
</code></pre>
<p>Your output should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-06.07.12.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Retrieving Services in Namespace</em></p>
<p>This output displays the services in the "database" namespace, including a service named "postgres" with the type "LoadBalancer," its internal cluster IP, external IP, and port mappings. You'll utilize the external IP along with the Postgres port "5432" to connect your database with the Node.js application. Note that your external IP will differ from mine.</p>
<h2 id="heading-nodejs-application">Node.js Application</h2>
<p>In this section, I'll guide you through setting up your Node.js app to connect to a PostgreSQL database in your Azure Kubernetes Service.</p>
<p>We'll cover sending data into the database and retrieving it using Postman. Also, I'll demonstrate how to check if the data remains in the database even if the pod running PostgreSQL in the cluster is deleted.</p>
<h3 id="heading-configure-your-nodejs-application">Configure your Node.js application</h3>
<p>Go to the database folder and open the database.js file. Replace the host with your EXTERNAL-IP obtained from the service, and leave the rest unchanged since you've already defined those variables in your config map.</p>
<p>Your database.js file should resemble the CodeSnap below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/code.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>CodeSnap of database.js Configuration</em></p>
<h3 id="heading-run-your-nodejs-application">Run your Node.js application</h3>
<p>In your VS Code terminal, execute this command to start the Node.js application locally:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>Your output should look like this if the connection is established successfully.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-06.27.25.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Server Listening on Port 4000</em></p>
<p>If your output looks the same as mine, it indicates that you've successfully connected your Node.js application to the PostgreSQL database running in your Azure Kubernetes cluster. Congratulations! 🎉</p>
<h3 id="heading-test-the-application">Test the application</h3>
<p>Testing is a fundamental principle in DevOps operations. It helps us understand the state of the application we've built before releasing it to users. Any application that doesn't pass the testing stage will not be deployed. This is a rule in DevOps.</p>
<p>For this tutorial, you'll be using Postman. You can download Postman <a target="_blank" href="https://www.postman.com/downloads/">here</a>. Postman enables you to test API endpoints by receiving status responses.</p>
<p>Check out this <a target="_blank" href="https://qalified.com/blog/postman-for-api-testing/">post</a> on how to use Postman to test APIs. If you want to learn more, <a target="_blank" href="https://www.freecodecamp.org/news/learn-how-to-use-postman-to-test-apis/">here's a full course</a> on the subject.</p>
<h3 id="heading-open-your-postman-application">Open your Postman application</h3>
<p>To begin using Postman, start by creating a new API request in your preferred workspace. Choose POST. POST requests add new data to the database or server. Then, paste the endpoint URL (localhost:4000/api/v1/admin/register) for your Postman test.</p>
<p>The below screenshot illustrates how you will create a POST request.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-15.32.32.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman URL Endpoint Configuration</em></p>
<p>In the body, paste the JSON data shown below inside it as shown below:</p>
<pre><code class="lang-bash">{   
    <span class="hljs-string">"fullName"</span>:<span class="hljs-string">"Azure postgres freecodecamp"</span>,
    <span class="hljs-string">"email"</span>:<span class="hljs-string">"freecodecamp@gmail.com"</span>,
    <span class="hljs-string">"password"</span>:<span class="hljs-string">"freecodecamp"</span>
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-15.34.58-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman Request Body</em></p>
<p>Once you've set up the request, just click the "Send" button to send it. Postman will then show you status codes, and the response payload as shown below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-06.56.39.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman API Response</em></p>
<h3 id="heading-confirm-the-data">Confirm the data</h3>
<p>To confirm that the data you sent into the database exists, make a GET request to this endpoint URL: localhost:4000/api/v1/admin/freecodecamp@gmail.com</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-15.45.41.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman GET Request URL Endpoint</em></p>
<p>When you click send, Postman will then show you status codes and the response payload as shown below. Notice that we didn't put anything in the body because this is a GET request.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-15.48.29.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman GET Request by Email Retrieval Response</em></p>
<h3 id="heading-delete-the-pod-to-confirm-data-persistence">Delete the pod to confirm data persistence</h3>
<p>We chose to create our PostgreSQL database using a <code>StatefulSet</code> to ensure that data persists even if the pod is destroyed. Let's test this by deleting the pod and checking if the data remains intact.</p>
<p>In your VS Code terminal, execute the command: <code>kubectl delete pod -n database postgres-0</code>.</p>
<p>This command deletes a pod named "postgres-0" in the "database" namespace from your Kubernetes cluster. Your output should look like this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-07.09.41.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Deleting Pod in Namespace:</em></p>
<h3 id="heading-pod-recreation">Pod recreation</h3>
<p>Kubernetes has a built-in feature called replication controllers or replica sets that ensure a specified number of pod replicas are running at any given time. If a pod is deleted, Kubernetes will automatically recreate it to maintain the desired number of replicas, ensuring high availability</p>
<p>If you run <code>kubectl get pods -n database</code>, you'll notice that Kubernetes has created a new pod with the same name, "postgres-0", to replace the one that was deleted. This ensures that the application remains available and continues to function as expected.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-07.10.04.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Pod Recreated back in Namespace</em></p>
<h3 id="heading-data-persistence">Data persistence</h3>
<p>Navigate back to Postman and make a GET request to the endpoint URL localhost:4000/api/v1/admin/freecodecamp@gmail.com.</p>
<p>You should get the same response as before. So under the hood, when we delete the pod, the storage disk was not deleted. The storage disk is inside the Azure disk. How do we know that? If you run this command:</p>
<pre><code class="lang-bash">kubectl get pvc -n database
</code></pre>
<p>you should get this output:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-16.04.48-3.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Persistennce Volume Claim details in namespace</em></p>
<p>This shows details about a storage called "azure-managed-disk-pvc" in your Kubernetes. It's currently in use and has 4 gigabytes of space available. It's set up to be read and written to by one system at a time. This storage is provided by a service called "azuredisk-premium-retain" that we configured earlier.</p>
<h3 id="heading-clean-up-resources">Clean up resources</h3>
<p>In this tutorial, you created Azure resources in a resource group. If you won't need these resources later, delete the resource group from the Azure portal or run the following command in your terminal:</p>
<pre><code class="lang-bash">az group delete --name AZURE-POSTGRES-RG --yes
</code></pre>
<p>This command might take a minute to run.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We've gone on quite a journey here! You've learned how to deploy a Postgres container in Azure Kubernetes Service (AKS) and integrate it with a Node.js application.</p>
<p>In this tutorial, I guided you through the process of configuring Kubernetes using Azure Kubernetes Service (AKS). You learned to customize YAML files utilizing StatefulSet, Persistent Volume, and Services to deploy a PostgreSQL database on Azure Kubernetes. You also acquired PostgreSQL database credentials running within AKS to establish connectivity with a Node.js application. I then provided detailed instructions on connecting your Node.js Express app to the Postgres container within the AKS cluster.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build and Push Dockerized Applications to the Azure Registry ]]>
                </title>
                <description>
                    <![CDATA[ A common issue on development teams is that an application might work on one developer’s computer but not on the others. Luckily, there's Docker, a popular containerization technology. It works by packing up all the components needed to run the appli... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-and-push-dockerized-applications-to-azure-registry/</link>
                <guid isPermaLink="false">66b906a6e8c4e204927a90f1</guid>
                
                    <category>
                        <![CDATA[ Azure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ container ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Destiny Erhabor ]]>
                </dc:creator>
                <pubDate>Wed, 29 Mar 2023 21:53:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/03/pexels-pixabay-163726--2-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A common issue on development teams is that an application might work on one developer’s computer but not on the others.</p>
<p>Luckily, there's Docker, a popular containerization technology. It works by packing up all the components needed to run the application and then executing the application in an isolated environment. This helps tackle the problem of "it works on my machine but not yours."</p>
<p>You just have to save the application in the Docker registry so that it can be downloaded and used by other developers. In this article, you will learn how to build a web application Docker image and push it to an Azure private registry.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-how-to-get-started">How to Get Started</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-a-docker-image">How to Build a Docker image</a></li>
<li><a class="post-section-overview" href="#how-to-run-and-test-a-new-container-locally-from-the-image">How to Run and Test a New Container Locally from the Image</a></li>
<li><a class="post-section-overview" href="#heading-how-to-push-the-image-to-azure-registry">How to Push the Image to Azure Registry</a></li>
<li><a class="post-section-overview" href="#heading-clean-up">Clean Up</a></li>
<li><a class="post-section-overview" href="#heading-summary">Summary</a></li>
</ul>
<h2 id="heading-how-to-get-started">How to Get Started</h2>
<p>Before attempting to follow along with this tutorial, you should have the following:</p>
<ul>
<li>Docker installed – You can install Docker from the <a target="_blank" href="https://www.docker.com/community-edition">Docker website</a>.</li>
<li>An Azure Subscription – You can <a target="_blank" href="https://azure.microsoft.com/en-us/free/?WT.mc_id=academic-75638-bethanycheum">sign up</a> for a free Azure account if you don’t already have one.</li>
<li><a target="_blank" href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli">Azure CLI</a> – This tutorial requires the Azure command-line interface (CLI) to interact with the Azure container registry.</li>
<li>Basic understanding of Docker commands and containers.</li>
</ul>
<h2 id="heading-how-to-build-a-docker-image">How to Build a Docker Image</h2>
<p>A Docker image is a fundamental component and consists of a text file called a <strong>Dockerfile</strong>. This contains the application files and binaries that you wish to containerize.</p>
<p>In this guide, you will examine an example that lets you create a Docker image that has an Apache web server and a web application in order to learn how to create a Docker image.</p>
<h3 id="heading-how-to-create-a-docker-image-from-a-dockerfile">How to Create a Docker image from a Dockerfile</h3>
<p>You will first develop an HTML page that serves as your web application before writing a Dockerfile. As a result, you will create a new directory called <code>appdocker</code> and within it, create a new file called “index.html”. Add the following code to index.html:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome to my web app<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This page demo how to create, build and push a dockerized application to azure container registrar, (ACR) <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>You should now create a Dockerfile (without an extension) in the same directory with the content listed below to build your web image, which we will cover in more detail next.</p>
<pre><code class="lang-docker"><span class="hljs-keyword">FROM</span> httpd:latest
<span class="hljs-keyword">LABEL</span><span class="bash"> Owner <span class="hljs-string">'Destiny Erhabor'</span></span>
<span class="hljs-keyword">COPY</span><span class="bash"> index.html /usr/<span class="hljs-built_in">local</span>/apache2/htdocs/</span>
</code></pre>
<ul>
<li>Every Docker image is created from another Docker image. The <code>FROM httpd:latest</code> at the start of the Dockerfile is necessary and indicates the Apache base image you will use for your own.</li>
<li>The <code>LABEL Owner 'Destiny Erhabor'</code> gives you an option to specify the image owner. In this case you should use your name.</li>
<li>The image creation procedure is then carried out using the <code>COPY index.html /etc/nginx/nginx.conf</code> instruction. The local index.html page that you created is copied by Docker into the image's /usr/local/apache2/htdocs/ directory.</li>
</ul>
<p>Now, to build the Docker image, open a terminal to where the Dockerfile is located in the folder “appdocker”, and then run the docker build command:</p>
<pre><code>docker build -t mywebapp:v1 .
</code></pre><p><img alt="docker build" width="600" height="400" loading="lazy">
<em>docker build</em></p>
<p>You can also check list of the images created by executing the command:</p>
<pre><code>docker images
</code></pre><p><img alt="docker images" width="600" height="400" loading="lazy">
<em>docker images</em></p>
<p>The three steps of the Docker image builder are as follows, which you can see in the execution above:</p>
<ol>
<li>The base image is downloaded by Docker in step one.</li>
<li>Docker copies the image's index.html file.</li>
<li>The image is created and tagged by Docker.</li>
</ol>
<h2 id="heading-how-to-run-and-test-a-new-container-locally-from-the-image">How to Run and Test a New Container Locally from the Image.</h2>
<p>You will use the <code>docker run</code> command in your terminal to create a new container using your Docker image as follows:</p>
<pre><code>docker run -d --name mywebapp -p <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> mywebapp:v1
</code></pre><ul>
<li>The name of the container you desire is specified in the <code>--name</code> parameter. </li>
<li>You define the desired port translation with the <code>-p</code> argument. In your example, this would entail that our local machine's port 8080 would be translated from port 80 of the container. </li>
<li>The name of the image and its tag make up the command's final parameter.</li>
</ul>
<p>You can test your container on your local system on the port translation that we previously performed. To achieve this, launch a browser and type <a target="_blank" href="http://localhost:8080/">http://localhost:8080</a>.</p>
<p><img alt="testing image locally" width="600" height="400" loading="lazy">
<em>testing image locally</em></p>
<h2 id="heading-how-to-push-the-image-to-azure-registry">How to Push the Image to Azure Registry</h2>
<p>The primary repository for shared Docker images is called the Docker Registry. This registry can either be public, such as Docker Hub or private, such as Azure Container Registry.</p>
<p>Private Docker container images and other relevant artifacts can be stored and managed on the Microsoft-owned Azure Container Registry (ACR). These images can then be downloaded and used for local or hosting platform container-based deployments.</p>
<p>You'll need to go through the following steps to push your image into ACR:</p>
<p>First, you will launch the azure CLI command below in your terminal to connect to your Azure account:</p>
<pre><code>az login
</code></pre><p>Next, you’ll use the Azure CLI to create a resource group, a container that manages multiple Azure resources using <code>az group create</code> and the azure container registry using the <code>az acr</code> commands:</p>
<pre><code>az group create --name RG-ACR --location eastus
</code></pre><p>The <code>--name</code> tag specifies the resource group name as <code>RG-ACR</code> and <code>--location</code> gives the region where it is located as <code>eastus</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/resource-group-az.png" alt="Image" width="600" height="400" loading="lazy">
<em>Resource group</em></p>
<pre><code>az acr create --resource-group RG-ACR --name mywebappacrdemo --sku Basic
</code></pre><p>The resource group you created earlier is required to manage the acr resource – hence the <code>--resource-group</code> tag. The <code>--name</code> specifies the name of the registry (the name must be <strong>globally unique</strong>) and the <code>--sku</code> provides the several service tiers. </p>
<p>These tiers offer a range of options for the capacity and use of your private Docker registry together with predictable pricing. Here you are using Basic tiers.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/azure-acr.png" alt="Image" width="600" height="400" loading="lazy">
<em>ACR resources creation</em></p>
<p>Now that you are logged in, use the command to establish a connection to the ACR resource you created above by passing the <code>--name</code> parameter as the name of that resource:</p>
<pre><code>az acr login --name mywebappacrdemo
</code></pre><p>When the command is successful, a Login Succeeded message is returned.</p>
<p>Finally, run a few instructions to push the mywebapp Docker image into your ACR resource.</p>
<p>The initial action is to tag the image with the fully qualified name of the login server for the registry. The format of the fully qualified name is . azurecr.io, as show below:</p>
<pre><code>docker tag mywebapp:v1 mywebappacrdemo.azurecr.io/mywebapp:v1
</code></pre><p>Now push the image into the ACR resource.</p>
<pre><code>docker push mywebappacrdemo.azurecr.io/mywebapp:v1
</code></pre><p><img alt="Image" width="600" height="400" loading="lazy">
<em>ACR resource in azure portal</em></p>
<h2 id="heading-clean-up"><strong>Clean Up</strong></h2>
<p>To avoid paying for the underlying Azure cost, you should clean up your resources if they're not being used.</p>
<p>To clean up your Azure resources, in the Azure portal go to or search for resources group, find the one you just created, and delete it. This will delete all resources in the resource group.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, we looked at the primary tasks that go into building and running a Docker image for a web application.</p>
<p>You also learned how to push a Docker image to the Azure container registry using the Azure CLI.</p>
<p>As always, I hope you enjoyed the article and learned something new. If you want, you can follow me on <a target="_blank" href="https://www.linkedin.com/in/destiny-erhabor">LinkedIn</a> or <a target="_blank" href="https://twitter.com/caesar_sage">Twitter</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Docker vs Virtual Machine (VM) – Key Differences You Should Know ]]>
                </title>
                <description>
                    <![CDATA[ In this guide, you'll learn the differences between a virtual machine and a Docker container. Both virtual machines and containers help replicate the development environment, and manage dependencies and configurations better. But there are certain di... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-vs-vm-key-differences-you-should-know/</link>
                <guid isPermaLink="false">66bb8b26c332a9c775d15b68</guid>
                
                    <category>
                        <![CDATA[ container ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ virtual machine ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Bala Priya C ]]>
                </dc:creator>
                <pubDate>Tue, 04 Oct 2022 16:48:57 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/07/docker-vs-vm-diff.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this guide, you'll learn the differences between a <strong>virtual machine</strong> and a <strong>Docker</strong> container.</p>
<p>Both virtual machines and containers help replicate the development environment, and manage dependencies and configurations better. But there are certain differences you should be aware of that will help you choose a VM or a Docker container depending on the application.</p>
<p>Over the next few minutes, we'll go over how virtual machines and Docker containers work, and then summarize the key differences between the two.</p>
<p>Let's begin!</p>
<h2 id="heading-challenges-in-application-development-and-deployment">Challenges in Application Development and Deployment</h2>
<p>When you work as part of a development team, each application requires installation of multiple third-party software and packages. In order to collaborate and work together, every developer on the team should configure their local development environment.</p>
<p>However, setting up the development environment is a tedious process. The installation steps can be potentially different depending on the operating system and system configuration. Even during deployment, you have to configure the same environment on the server.</p>
<p>Different applications also require multiple versions of a specific software, say, PostgreSQL. In such cases, managing dependencies across applications becomes difficult.</p>
<p>To address the above challenges, it really helps if the applications run in isolated environments that you can replicate easily—independent of the system configuration. Both Virtual Machines (VMs) and Docker containers help you achieve this. Let's learn how!</p>
<h2 id="heading-how-does-a-virtual-machine-work">How Does a Virtual Machine Work?</h2>
<p>A <strong>Virtual Machine</strong> or <strong>VM</strong> is the emulation of a physical computer inside a host machine. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/1.png" alt="Image" width="600" height="400" loading="lazy">
<em>How a VM works (image by the author)</em></p>
<p>Running on top of the host operating system is a piece of software called a hypervisor that controls the VM instances. Each VM instance has its own guest operating system. The applications run inside this isolated environment. </p>
<p>You can have multiple VMs, each running a different application on a different operating system.</p>
<h2 id="heading-how-does-a-docker-container-work">How Does a Docker Container Work?</h2>
<p>Recently, container technology has revolutionized the software development process and the way development and operation teams work together. With time, Docker has become the go-to choice for containerizing applications.</p>
<p>Dockers containers are analogous to physical containers that you can use to store, package, and transport goods. But instead of tangible goods, they’re containers for software applications. 🙂</p>
<p>A docker container is a portable unit of software—that has the application—along with the associated dependency and configuration. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/2.png" alt="Image" width="600" height="400" loading="lazy">
<em>How containers work (image by the author)</em></p>
<p>Unlike a VM, Docker containers <em>do not</em> boot up their own guest OS. Rather, they run on top of the host operating system. This is facilitated by a container engine.</p>
<h2 id="heading-docker-vs-vm-a-comprehensive-comparison">Docker vs VM – A Comprehensive Comparison</h2>
<h3 id="heading-1-virtualization">1️⃣ Virtualization</h3>
<p>From our understanding thus far, both virtual machines and Docker containers provide isolated environments to run applications. The key difference between the two is in <em>how</em> they facilitate this isolation.</p>
<p>Recall that a VM boots up its own guest OS. Therefore, it virtualizes both the operating system kernel and the application layer. </p>
<p>A Docker container virtualizes <em>only</em> the application layer, and runs on top of the host operating system.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/3.png" alt="Image" width="600" height="400" loading="lazy">
<em>Container vs VM (image by the author)</em></p>
<h3 id="heading-2-compatibility">2️⃣ Compatibility</h3>
<p>A virtual machine uses its own operating system and is <em>independent</em> of the host operating system that it’s running on.  Therefore, a VM is compatible with all operating systems. </p>
<p>A Docker container, on the other hand, is compatible with <em>any</em> Linux distribution. You may run into some problems running Docker on a Windows machine or an older Mac.</p>
<h3 id="heading-3-size">3️⃣ Size</h3>
<p>A Docker image is lightweight and is typically in the order of kilobytes. </p>
<p><strong>💡 Note</strong>: A Docker image denotes the artifact containing the application, its associated dependencies, and configuration. A running instance of the Docker image is called a container.</p>
<p>A VM instance can be as large as a few gigabytes or even terabytes.</p>
<h3 id="heading-4-performance">4️⃣ Performance</h3>
<p>In terms of performance, Docker containers provide near-native performance. Because they are lightweight, you can start them in a few milliseconds. </p>
<p>Starting a VM is equivalent to setting up a standalone machine inside your computer. It can take as long as a few minutes to start a VM instance.</p>
<h3 id="heading-5-security">5️⃣ Security</h3>
<p>Docker containers run on top of the host operating system. Therefore, if the host OS is susceptible to security vulnerabilities, so are the Docker containers.</p>
<p>Virtual machines, on the other hand, boot up their own operating system, and are more secure. Recall: each virtual machine is a fully blown machine running inside another. If you have stringent security constraints to be met for sensitive applications, you should consider using a virtual machine instead.</p>
<h3 id="heading-6-replicability">6️⃣ Replicability</h3>
<p>The next factor we'll consider is the ease with which you can replicate the isolated environments provided by VMs and containers. We can infer the ease of replicability from our earlier discussions on <strong>size</strong> and <strong>performance</strong>. </p>
<p>When there are multiple applications, each of which should run on a VM instance, using VMs can be <strong>inefficient</strong> and <strong>resource intensive</strong>. Docker containers, by virtue of being lightweight and performant, are preferred when you need to run multiple applications. ✅</p>
<h2 id="heading-summing-up">Summing Up</h2>
<p>I hope this tutorial helped you understand how Docker containers and VMs work, and the key differences between the two. </p>
<p>Here's a summary of what you've learned:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Docker</td><td>Virtual Machine (VM)</td></tr>
</thead>
<tbody>
<tr>
<td>Compatibility</td><td>Works best with Linux distributions</td><td>All operating systems</td></tr>
<tr>
<td>Size</td><td>Light in weight</td><td>Substantially larger – of the order of Gigabytes or more</td></tr>
<tr>
<td>Virtualization</td><td>Only the applications layer</td><td>Both the OS kernel and applications layers</td></tr>
<tr>
<td>Performance</td><td>Easy to start containers (typically takes milliseconds)</td><td>Takes longer to start a VM instance</td></tr>
<tr>
<td>Security</td><td>Less secure</td><td>Relatively more secure</td></tr>
<tr>
<td>Replicability</td><td>Easy to replicate. You can pull Docker images corresponding to the various applications</td><td>Difficult to replicate, especially with increasing number of VM instances</td></tr>
</tbody>
</table>
</div><p>Thank you for reading this far. See you all soon in another tutorial! 😄</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Container Orchestration? How to Manage your Containers with MicroK8s ]]>
                </title>
                <description>
                    <![CDATA[ Container orchestration has been called the next big thing in the world of technology. And it’s easy to see why. Container orchestration helps IT professionals and programmers maximize their applications’ performance. It helps them ensure that multip... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/container-orchestration-for-beginners/</link>
                <guid isPermaLink="false">66ba61084b814a3715945390</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ valentine Gatwiri ]]>
                </dc:creator>
                <pubDate>Mon, 12 Sep 2022 14:57:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/09/pexels-pixabay-163726.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Container orchestration has been called the next big thing in the world of technology. And it’s easy to see why.</p>
<p>Container orchestration helps IT professionals and programmers maximize their applications’ performance. It helps them ensure that multiple containers can work together to handle more tasks at the same time than any one container could manage on its own. </p>
<p>But how does container orchestration work, exactly? What are its advantages, and how do you go about implementing it? This article will provide answers to all of your queries and more.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To understand container orchestration, you need:</p>
<ul>
<li>Ubuntu 22.04 LTS, or any other version is OK.</li>
<li>Basic understanding of what containers are and how they function</li>
<li>A consistent internet connection</li>
<li>Sudo permissions</li>
</ul>
<h2 id="heading-what-you-will-learn">What You Will Learn</h2>
<p>If you're unfamiliar with container orchestration, you may be wondering what the fuss is about. </p>
<p>In this tutorial, we'll go through the advantages of container orchestration and how to apply it in your organization. </p>
<p>By the end, you'll better understand why container orchestration is so essential and how it can help your business run more efficiently.</p>
<h2 id="heading-what-is-container-orchestration">What is Container Orchestration?</h2>
<p>If you’re running a business, chances are you’re using containers to run your applications. But what actually is container orchestration? </p>
<p>In short, it’s a way to manage and automate the deployment, scaling, and management of containers.</p>
<h2 id="heading-the-benefits-of-using-a-container-orchestrator">The Benefits of Using a Container Orchestrator</h2>
<p>There are many benefits to using a container orchestrator, including increased efficiency, scalability, and portability. </p>
<p>A container orchestrator can also help you manage your application's lifecycle, making it easier to deploy and update your applications. In addition, a container orchestrator can help you automate tasks such as monitoring and logging.</p>
<p>With a container orchestrator, you can define the resource constraints for each of your containers. For example, if you need more CPU power for one of your containers than another, the container orchestrator will allocate resources accordingly.</p>
<h2 id="heading-how-to-pick-your-container-orchestration-platform">How to Pick Your Container Orchestration Platform</h2>
<p>There are a few things you should consider when selecting a container orchestration platform. The first is whether you want a self-hosted or cloud-based solution. A cloud-based solution may be the ideal option if you're just getting started with containers.</p>
<p>Another thing to consider is what features you require. Some platforms offer more comprehensive management tools than others. </p>
<p>Finally, think about how easy the platform is to use and whether it integrates well with other tools you're using.</p>
<h2 id="heading-overview-of-an-example-stack">Overview of an Example Stack</h2>
<p>In a typical container orchestration setup, you will have several different components working together to provide a complete solution. </p>
<p>For example, you might have:</p>
<ul>
<li>a container registry, where your images are stored</li>
<li>a container runtime, which manages the lifecycle of your containers </li>
<li>and a container orchestration platform, which provides scheduling and coordination for your containers.</li>
</ul>
<p>Some use cases that require an orchestrated approach include continuous integration/deployment (CI/CD) and batch processing.</p>
<p>A CI/CD pipeline is an automated system that helps developers release new features into production at any time by reducing manual tasks like deployment scripts and configuration management.</p>
<p>A batch processing workload is one where many compute-intensive tasks share resources during specific periods, such as on weekends or after hours when demand is low.</p>
<p>One way to execute these tasks would be with a queue, but this method does not scale well. To process more jobs in parallel, you need a scheduler capable of managing hundreds or thousands of concurrent jobs.</p>
<p>Batch processing also has strict requirements for data consistency: it cannot tolerate a high degree of variability in execution times between individual jobs because there may be some dependencies between them. </p>
<p>Scheduling algorithms that can reduce variability in execution times by intelligently managing the order in which they run their jobs are preferable here.</p>
<h2 id="heading-how-to-plan-your-implementation">How to Plan Your Implementation</h2>
<h3 id="heading-step-1-decide-on-architecture">Step 1 – Decide on Architecture</h3>
<p>Now that you know what container orchestration is and why you need it, it's time to start planning your implementation. </p>
<p>The first step is deciding on the architecture of your system. This will involve deciding how many nodes you need, what type of storage each node will use, and how the nodes will be interconnected. </p>
<p>Once you have a good understanding of your desired architecture, you can begin looking at different orchestration solutions that will fit your needs.</p>
<p>There are two common ways you can orchestrate your containers: scheduler-based orchestration and resource-based orchestration. </p>
<p>In scheduler-based orchestration, an external scheduler decides when and where containers should run. In resource-based orchestration, allocating resources is done internally by the orchestrator based on preconfigured policies.</p>
<p>If you want more control over the placement of containers, then scheduler-based orchestration may be better for you. If you want less overhead concerning configuring resources, then resource-based orchestration may be more appropriate.</p>
<h3 id="heading-step-2-preparation">Step 2 – Preparation</h3>
<p>As your company expands, you'll need to consider how to scale. </p>
<p>One method is to use container orchestration. This allows you to manage and deploy your containers more efficiently. You can even set up an automation that will automatically take care of scaling for you as needed.</p>
<h3 id="heading-step-3-putting-it-all-together">Step 3 – Putting it All Together</h3>
<p>Now that you know the basics of container orchestration and how it can benefit your business, it's time to put it all together. Here are the steps you'll need to take:</p>
<ol>
<li>Define your goals and objectives. What do you want to achieve with container orchestration?</li>
<li>Choose the right tool for the job. There are various container orchestration tools available, so do your research to find the one that best fits your needs.</li>
<li>Set up your environment. Getting your containers set up can be challenging if you don't know what you're doing. Be sure to read through the documentation and follow any instructions before beginning.</li>
<li>Test it out! Try running your app with a new orchestration system before fully committing. The last thing you want is something not to work or go as planned once it's already been implemented on production servers. Fortunately, testing things out beforehand will help you minimize any surprises in the future.</li>
</ol>
<p>Now that you know the basics of container orchestration at a high level, it's time to get started!</p>
<h2 id="heading-types-of-container-orchestration-platforms">Types of Container Orchestration Platforms</h2>
<p>There are three main types of container orchestration platforms: Kubernetes, Docker Swarm, and Apache Mesos. Each has advantages and downsides, so it is critical to select the best one for your purposes.</p>
<p>For example, if you're a startup or small business with limited IT resources, then you might want to use Docker Swarm. It is designed for teams that don't have many systems administrators.</p>
<p>For larger enterprises with more experienced IT staff, Kubernetes is a good choice because it is more scalable and provides more control over how containers are deployed. But both Kubernetes and Docker Swarm provide excellent scalability as well as a high degree of user-friendliness.</p>
<p>In this tutorial, we will be working with Kubernetes. Kubernetes is an open-source container orchestrator. By orchestrating your containers with Kubernetes, you can automate many of the tasks associated with managing them.</p>
<h2 id="heading-how-to-install-kubernetes-as-a-single-node">How to Install Kubernetes as a Single Node</h2>
<p>One option for getting started with Kubernetes is to install it as a single node. This can be a great way to learn the basics of the system and get a feel for how it works. Plus, it's relatively simple to set up.</p>
<p>Kubernetes is an open platform that allows you to make many decisions on your own, which can be really helpful.</p>
<p>In the following tutorial, I decided to use:</p>
<ul>
<li>Ubuntu 22.04 LTS. If you are using any other operating system you can learn more <a target="_blank" href="https://kubernetes.io/docs/home/">here</a>.</li>
<li>MicroK8s. From a single node, MicroK8s creates a certified Kubernetes cluster in just a few minutes. Microk8s Kubernetes distribution from Canonical is small, versatile, and portable.</li>
</ul>
<h3 id="heading-step-1-use-the-snap-package-to-install-microk8s">Step 1: Use the snap package to install MicroK8s.</h3>
<p>MicroK8s is distributed as a snap, which necessitates the installation of snapd. This is already included in the most recent version of Ubuntu. </p>
<p>Type the following command to get the most recent version of MicroK8s:</p>
<pre><code class="lang-bash">sudo snap install microk8s --classic
</code></pre>
<p>After running the command above. It will begin downloading MicroK8s as shown in the image below.</p>
<p><img src="https://lh4.googleusercontent.com/UVy9niChsvxmgf_NYAB7km-QaP6b8zOZpzhPqrxQaUIMrKQJVb1EjIjyVAdFnz1-4Ym_Ps3U57Gm5iviWjNlIDwpUN_2Fok0odBJ_QAjBniCqe9PcoopG1EKrbCMGug5VI_foyqWplCjqJD6NXwuzazp_GLZ8UV8Nhkrz_yIPI__tdXVJnlAZt_Nuw" alt="Image" width="731" height="113" loading="lazy"></p>
<h3 id="heading-step-2-on-your-ubuntu-system-list-the-various-versions-of-microk8s">Step 2: On your Ubuntu system, list the various versions of Microk8s.</h3>
<p>You may use the snap command below to see all possible versions of microk8s.</p>
<pre><code class="lang-bash">snap info microk8s
</code></pre>
<p>When you execute the command above, you should get the output seen in the image below:</p>
<p><img src="https://lh5.googleusercontent.com/CSt0TpWVz5w3Fm2nXtAHKSiXtHWdiz9K1tLJaFc3paBgp_EB_aiJluPDWJdOgMOf3TdnkemMIkOhXoL1rSP-klvo7xGB5GJSfPYC6vTMTrdyJEM86G3f91YAfePsW1PwQLb2HBKVFIyyDRszjxj0mp4FdPfTTODZ9slh9xeTRMTgIVt8YfZiCkWNkw" alt="Image" width="731" height="422" loading="lazy"></p>
<h3 id="heading-step-3-check-that-microk8s-has-been-installed">Step 3: Check that MicroK8s has been installed.</h3>
<p>You can check the status of MicroK8s using an existing command in Ubuntu. To accomplish this, enter the following command in your terminal.</p>
<pre><code class="lang-bash">sudo microk8s status --wait-ready
</code></pre>
<p>Note that to wait for Kubernetes services to start, you must use the "-wait-ready" parameter during installation.</p>
<p>After executing the command you will get the following output:</p>
<p><img src="https://lh5.googleusercontent.com/UGEjgMIJCeT5DdI8rzNgg1uq6fKruooYsrtX70b9dOwA09AIzLHNfTdn939HDFLOO40pliraXPjmEpUHwgmnLHwvGnCvbdI7_kd9I74A0o3ZF0dllsuUTlb8VlbfrHt_xBc5aXJxJaTsaUBcJiCsWC5KWui-sBo9KBPPNZeCX1W8NT5UnyGU8wtVAA" alt="Image" width="795" height="477" loading="lazy"></p>
<h3 id="heading-step-4-connect-to-kubernetes">Step 4: Connect to Kubernetes</h3>
<p>The most critical step is now to gain access to Kubernetes. MicroK8s offers its version of kubectl for interacting with Kubernetes. It is capable of running commands that track and control your Kubernetes cluster. </p>
<p>Enter the following command into the terminal to see your current node.</p>
<pre><code class="lang-bash">sudo microk8s kubectl get nodes
</code></pre>
<p>After running the command above you will get the following output. As you can see, the status is "Ready". By using this command, you may also look at the node's Name, roles, age, and version.</p>
<p><img src="https://lh6.googleusercontent.com/3x_1XVpFrAMNmifEuN1qUHYPsphakd_73WyW-FkAIfKeNnlfPaHctS3WqyQnUponS3yrk4PuO45cnBc-H0coKMJ_TXodapUsanWgZ6FakcoPbXga1eCFy7XOdlCZcgaASPiZz9_ogSQMYjKKK3tKzpZrkRVRFdFNlTX8D7zbRyCCyVH3xSvB_mD7GA" alt="Image" width="795" height="102" loading="lazy"></p>
<h3 id="heading-ia"> </h3>
<p>Step 5: Examine the running services</p>
<p>If you wish to see what MicroK8s services are currently running, use the following command:</p>
<pre><code class="lang-bash">sudo microk8s kubectl get services
</code></pre>
<p>This command displays the name, type, Cluster-IP, external-IP, port(s), and age of the currently running services.</p>
<p><img src="https://lh6.googleusercontent.com/XhlBLlIvb6SDUbc9TTGe9JFLJu02k6QbEwIwklatTQ1dqhRp7SkKcXTDuWS6ouhwV3OSfLfUEresQId9Ht3h_bgBu-BmbhYqbyRabtNxzo6gOeAq_iNaH0XFoKtt2zEuT4y60YlLWqxt06ysusCDj8-EwRe2ZthyRuOTXVMxn6MCr6p31CVPysDAQw" alt="Image" width="795" height="121" loading="lazy"></p>
<h3 id="heading-step-6-deploy-the-application-with-mickrok8s">Step 6: Deploy the application with MickroK8s.</h3>
<p>In the following example, we are using kubectl to deploy a NGINX application. To successfully deploy NGINX, enter the following command:</p>
<pre><code class="lang-bash">sudo microk8s kubectl create deployment nginx --image=nginx
</code></pre>
<p>As you can see in the image below, the application has been deployed:</p>
<p><img src="https://lh6.googleusercontent.com/dAXljEprBhHjUpTd89cYZNgMllC19xMBV-wlBBUNSBGOsatHOWuWaZ1NScWoMRmcVmKKEhfeF397NUJ016MWfRHjyiAfr5PiyuUS1AAB91pmUAxaAV9lNsA4Olr_u5o28k6MGrsWQGkiLib-uZcFcxhX4DJ3viVsD9Iw-VTb_K220gUowJQRh5gisg" alt="Image" width="795" height="105" loading="lazy"></p>
<p>You may use the same command to deploy any other app.</p>
<p>Make sure you remember to replace <code>Nginx</code> with your preferred application name, as shown in the image below.</p>
<p><img src="https://lh4.googleusercontent.com/hbSgEc3qSZl74Sb1lY-NxuUrwRNuRcA-q--HR4x9rXGjO1RkprpQhRkihh7uvXVnqkvcLXkOwAFeZfhoCtWQRu9QtO-UOnNUBcqnwJGwsTyoLJD0nI9CeygGX5TRT0g8oQhq7iXoWjarb9EA6ipltCeHR9LSApOmw0w476sh3vvTfqi7ZgsyZU70lA" alt="Image" width="723" height="129" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If you're looking for a way to improve your application's efficiency and deployment, container orchestration is a great solution. </p>
<p>By using containers, you can package all the necessary components of your application into one easily-deployable unit. </p>
<p>Plus, by using a container orchestration tool like Kubernetes, you can automate the entire process. You don't have to worry about manually configuring anything; just write some code, put it in a container, and deploy it. </p>
<p>With this kind of automation, things are only going to get easier.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Docker Engine-like Custom Container without Any Software ]]>
                </title>
                <description>
                    <![CDATA[ This article will discuss every aspect of containers, including how they operate in the background and their various component elements. We will also discover why Docker is so lightning-fast. By the end, you'll be able to create your own custom conta... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-your-on-custom-container-without-docker/</link>
                <guid isPermaLink="false">66c4c5a6e7521bfd6862b3a9</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gursimar Singh ]]>
                </dc:creator>
                <pubDate>Wed, 10 Aug 2022 16:45:36 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/08/image-12-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>This article will discuss every aspect of containers, including how they operate in the background and their various component elements. We will also discover why Docker is so lightning-fast.</p>
<p>By the end, you'll be able to create your own custom container. We'll also examine why Kubernetes deprecated Docker and embraced CRI-O, as well as how to configure a multi-node Kubernetes cluster using CRI-O.</p>
<p>In the end, we will look at the list of available container runtimes.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-11.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-what-are-containers">What are containers</a>?</li>
<li><a class="post-section-overview" href="#heading-why-do-we-need-containers">Why do we need containers</a>?</li>
<li><a class="post-section-overview" href="#heading-whats-the-difference-between-containers-and-virtualization">What's the difference between containers and virtualization</a>?</li>
<li><a class="post-section-overview" href="#heading-is-there-a-standard-format-for-containers">Is there a standard format for containers</a>?</li>
<li><a class="post-section-overview" href="#heading-types-of-container-platforms">Types of container platforms</a></li>
<li><a class="post-section-overview" href="#heading-how-to-launch-a-container">How to launch a container</a></li>
<li><a class="post-section-overview" href="#heading-why-did-kubernetes-depreciate-docker">Why did Kubernetes deprecate Docker</a>?</li>
<li><a class="post-section-overview" href="#heading-challenges-of-using-docker">Challenges of using Docker</a></li>
<li><a class="post-section-overview" href="#heading-what-is-a-cri-container-runtime-interface">What is a CRI (Container Runtime Interface)</a>?</li>
<li><a class="post-section-overview" href="#heading-how-to-build-a-multi-node-cluster-using-cri-o">How to build a multi-node cluster using CRI-O</a></li>
</ol>
<p>Alright, let's dive in.</p>
<h2 id="heading-what-are-containers">What Are Containers?</h2>
<p>Containers allow you to reliably move software from one computing environment to another.  </p>
<p>The technology behind containers is nearly as old as that behind virtual machines. But the information technology industry didn't begin using containers until around 2013–14, when Docker, Kubernetes, and other innovations that disrupted the sector became popular. </p>
<p>Containers have emerged as a critical tool in the process of developing software. They can serve either as a replacement for Virtual Machines or as a supplement to them. </p>
<p>Containerisation helps developers construct apps more rapidly and securely while also deploying them more easily.</p>
<h2 id="heading-why-do-we-need-containers">Why Do We Need Containers?</h2>
<p>As we learned above, containers provide a solution to the problem of transporting software from one computer environment to another in a reliable manner. This can be just from a developer's workstation to a test environment, from a staging environment to production, or even from a real system in a data center to a private or public cloud virtual machine. </p>
<p>Containerization makes possible all of these transformations. And these are just some of the viewpoint alterations that can happen.</p>
<p>This quote gives a little perspective on why containers are helpful:</p>
<blockquote>
<p>"You’re going to test using Python 2.7, and then it’s going to run on Python 3 in production and something weird will happen.   </p>
<p>Or you’ll rely on the behavior of a certain version of an SSL library and another one will be installed.   </p>
<p>You’ll run your tests on Debian and production is on Red Hat and all sorts of weird things happen.   </p>
<p>The network topology might be different, or the security policies and storage might be different but the software has to run on it." – Solomon Hykes</p>
</blockquote>
<h2 id="heading-how-do-containers-solve-this-issue">How Do Containers Solve This Issue?</h2>
<p>A more straightforward interpretation is that a container is an all-encompassing runtime environment. </p>
<p>This means that a piece of software, together with all of its dependencies, libraries, other binaries, and configuration files, is packed and distributed to customers as a single package. </p>
<p>The application platform and its dependencies can be protected from the consequences of changes in operating system distribution and underlying infrastructure if they are bundled within containers.</p>
<h2 id="heading-whats-the-difference-between-containers-and-virtualization">What’s the Difference Between Containers and Virtualization?</h2>
<p>A virtual machine is a package that may be shared when employing virtualization technology. This package includes both the program and the operating system being used. </p>
<p>On top of a physical server running three virtual machines, you'd have an installation of a hypervisor, as well as three distinct operating systems.</p>
<p>On the other hand, a Docker server that hosts three containerized programs only needs to run a single operating system because all of the containers share the same kernel. The standard components of the operating system can only be read, but each container has its own mount or access mechanism, which allows it to store and retrieve data. </p>
<p>This hints that containers are far lighter than virtual machines and make considerably less use of their resources.</p>
<h2 id="heading-is-there-a-standard-format-for-containers">Is There a Standard Format for Containers?</h2>
<p>When CoreOS published its own App Container Image (ACI) specification in 2015, some people worried that the rapidly growing container movement might splinter into different Linux container formats. This was because ACI stood for "App Container Image."</p>
<p>In contrast, the Open Container Project, which would subsequently become the Open Container Initiative (OCI), was not made public until the latter half of the same year.</p>
<p>The <a target="_blank" href="https://opencontainers.org/">Open Container Initiative</a>, which the Linux Foundation leads, aims to establish an industry standard for container formats as well as container runtime software that is compatible with all operating systems (OCI).</p>
<p>Docker technology was used to develop the Open Container Project (OCP) guidelines, and Docker gave around 5 percent of their software to help get the effort off the ground.</p>
<h3 id="heading-what-is-open-container-initiative">What is Open Container Initiative?</h3>
<p>The Open Container Initiative (OCI) is an organization whose mission is to ensure that the fundamental aspects of container technology, such as the container's format, are standardized so that anyone can use them.</p>
<p>As a result, companies can concentrate their efforts on developing the supplementary software they need in order to use standardized containers in an enterprise or cloud environment (instead of developing competing technologies for containers). </p>
<p>Software components called container orchestration and management solutions, as well as container security systems, are essential components.</p>
<h2 id="heading-types-of-container-platforms">Types of Container Platforms</h2>
<p>As a result of the development and expansion of container technology, a number of different choices are currently available. </p>
<p>Docker is, without a doubt, the most common and widely used container platform available. </p>
<p>On the other hand, the landscape of container technologies includes other technologies, each of which has its own use cases and benefits.</p>
<p>We will look at the two most famous ones, i.e., Docker and Podman.</p>
<h3 id="heading-docker">Docker</h3>
<p>Docker is the container platform that's currently the most popular and is used the most widely. It allows you to develop and use Linux containers. </p>
<p>Docker is a piece of software that, by using containers, simplifies the processes of creating, deploying, and operating software applications. It does this by minimising the number of steps in each process. </p>
<p>Docker has gained support not just from the most major Linux distributions, such as Red Hat and Canonical, but also from large organisations, such as Microsoft, Amazon, and Oracle. </p>
<p>Virtually all businesses concerned with information technology and cloud computing use Docker.</p>
<h5 id="heading-docker-architecture-and-components">Docker Architecture and Components</h5>
<p>Docker is built on a client-server architecture. The Docker daemon enables the creation, operation, and distribution of Docker containers. </p>
<p>The Docker client and Docker daemon can interact through a REST application programming interface (API), UNIX sockets, or network interface.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-34.png" alt="Image" width="600" height="400" loading="lazy">
<em>Source: docs.docker.com</em></p>
<p>Docker's architecture is comprised of the following five primary components:</p>
<ol>
<li><strong>Docker Daemon</strong> manages Docker objects like images, containers, networks, and volumes. It also responds to Docker API inquiries.</li>
<li><strong>Docker Clients</strong> enables users to interact with Docker by allowing the user to connect with Docker. Docker client supplies a CLI interface that allows users to send application commands to a Docker daemon and start and halt such operations.</li>
<li><strong>Docker Host</strong> provides a complete software program execution and operation environment. This system comprises the Docker daemon, Images, Containers, Networks, and Storage components.</li>
<li><strong>Docker Registry</strong> maintains Docker Images. Docker Hub is a public registry, and by default, Docker is configured to use images saved on Docker Hub. You can use it to administer your own register.</li>
<li><strong>Docker Images</strong> are templates that can only be read and produced by following a Dockerfile's set of instructions. Images specify the appearance we want for our packaged program, its dependencies, and the processes that should run when the application is launched.</li>
</ol>
<h4 id="heading-resource-isolation-components-in-docker">Resource Isolation components in Docker</h4>
<h5 id="heading-namespaces">Namespaces:</h5>
<ul>
<li>PID namespace for process isolation.</li>
<li>NET namespace for managing network interfaces.</li>
<li>IPC namespace for managing access to IPC resources.</li>
<li>MNT namespace for managing filesystem mount points.</li>
<li>UTS namespace for isolating kernel and version identifiers.</li>
</ul>
<h5 id="heading-control-groups">Control groups</h5>
<ul>
<li>Memory cgroup that oversees accounting as well as restrictions and alerts</li>
<li>HugeTBL is a cgroup that keeps track of each process group's use of huge pages.</li>
<li>CPU group is responsible for regulating the time users and the system spend using CPU.</li>
<li>CPUSet cgroup lets you associate a group with a certain CPU. Utilized for real-time workloads and NUMA systems with localised memory for each CPU.</li>
<li>BlkIO cgroup for measuring and limiting the amount of blkIO each group produces.</li>
<li>the net cls and net prio cgroups are utilised for traffic control tagging.</li>
<li>Devices cgroup for accessing devices that can both read and write data.</li>
<li>Cgroup for the freezing of a group referred to as the freezer. It is useful for scheduling cluster batches, relocating processes, and troubleshooting.</li>
</ul>
<h5 id="heading-union-filesystem">Union Filesystem</h5>
<ul>
<li>Docker images are composed of layered filesystems, allowing them to be extremely lightweight and speedy. Union file systems are layering-based file systems.</li>
<li>Docker Engine has the ability to use several different versions of UnionFS, such as AUFS, btrfs, vfs, and devicemapper.</li>
<li>For executing five 250MB image containers, we would require 1.25GB of disc space if we didn't have UnionFS.</li>
</ul>
<p>The Docker interface may appear to be a mysterious black box holding a variety of unknown technologies when seen from the outside. Despite their relative obscurity, these technologies are both highly intriguing and useful.  </p>
<p>Despite the fact that we do not need to grasp these technologies in order to utilise Docker effectively, it is still beneficial to learn about and have an awareness of these technologies.   </p>
<p>If we have a deeper understanding of the instrument, it will be much easier for us to make the proper decisions, such as those regarding performance optimization or security implications.   </p>
<p>In addition, this facilitates the discovery of innovative new technologies, which may have many more uses for the organisation than initially thought.</p>
<h3 id="heading-just-a-note">Just a note:</h3>
<p>Docker does not require cgroupfs as the control group driver. The cgroup can be changed to systemd. </p>
<p>Docker's own control group manager is cgroupfs. Nevertheless, for most Linux distributions, systemd is the default init system, and systemd has tight interaction with Linux control groups. </p>
<p>For Kubernetes, it is recommended to use systemd, as utilising cgroupfs in conjunction with systemd appears to be suboptimal.</p>
<p>So systemd is preferable for cgroup management. kubelet is set to utilise systemd by default. Therefore, Docker should be modified to utilise the systemd driver. </p>
<h5 id="heading-docker-engine-sparked-the-containerization-movement">Docker Engine Sparked the Containerization Movement</h5>
<p>The Docker Engine is the de-facto container runtime for Linux and Windows Server platforms. </p>
<p>Docker develops simple tools and a uniform packaging strategy that encapsulates all application requirements into a container, which is then executed by Docker Engine. </p>
<p>The Docker Engine enables containerized apps to operate anywhere reliably on any infrastructure, resolving "dependency hell" for developers and operations teams and removing the "it works on my laptop!" issue.</p>
<h3 id="heading-podman">Podman</h3>
<p>Podman is RedHat's product. Docker and Podman are fairly comparable to one another in many ways. </p>
<p>Podman provides a Docker-compatible command-line interface that you can alias to the Docker command-line interface with the <code>$ alias docker = podman</code>. Also, Podman provides a socket-activated REST API service that makes it possible for remote apps to launch containers whenever they want. </p>
<p>Users of docker-py and docker-compose are able to connect with Podman as a service because this REST API also supports the Docker API.</p>
<p>By using the libpod library, Podman is able to handle the entirety of the container ecosystem, which includes pods, containers, container images, and container volumes. </p>
<p>Podman is distinct from Docker in that it does not require a server and has a lightweight and Daemon-less design. This means that it makes direct contact with runC to start containers, which eliminates the need for an overhead server.</p>
<p>Containers managed by Podman can either be operated by the root user or by a user with fewer privileges than root. </p>
<p>While working with Docker, the Docker Command Line Interface is how we interface with the daemon that Docker runs in the background. The daemon, which operates on containers and produces pictures, is where the majority of the program's functionality can be found. </p>
<p>This daemon runs with the permissions of the root user. This also suggests that a Docker container may have access to the file system of the host computer if the configuration is not done appropriately. </p>
<p>In contrast, the architecture of Podman makes it possible for us to collaborate with the user who is responsible for running the container, and this user does not need to have root access in order to execute the application. </p>
<p>When compared to containers that run with root capabilities, non-privileged containers provide a substantial advantage. This is because if an intruder breaks into a non-privileged container and flees, the intruder will still be an unprivileged host user. Using this approach provides an additional safeguard for our data.</p>
<p>Just replace Docker with Podman in the instructions to use it. It has the same commands as Docker.</p>
<pre><code>$ alias docker=podman
</code></pre><h5 id="heading-what-other-advantages-does-podman-have">What other advantages does Podman have?</h5>
<ul>
<li>Integrated support for systemd makes it possible to run container processes in the background without the need for a separate daemon process.</li>
<li>Provides us with the ability to build and manage Podami, a collection consisting of one or more functional containers. Because of this, future workload migration to Kubernetes and the orchestration of Podman containers is now possible.</li>
<li>It is possible to implement UID separation using namespaces, providing an additional layer of security isolation while containers are being executed.</li>
<li>Can create a YAML file for Kubernetes from a container that is currently operating (with the command <code>$ podman generate kube</code>).</li>
</ul>
<h2 id="heading-how-to-launch-a-container">How to Launch a Container</h2>
<p>To launch any container we need two things: Image and RunTime.</p>
<p>Container engines such as Docker and Podman are only an additional software layer that sits on top of the runtimes. They are not responsible for actually launching the containers themselves. </p>
<p>They also initiate interaction with the runtimes in the background to start the container processes.</p>
<p>A Container Runtime is a software that runs and manages the components required to run containers.</p>
<p>Runtime is actually a program/software which launches, deletes, and removes containers.</p>
<p>runC is a very famous Runtime, but we have many other Runtimes like gvisor and kata.</p>
<p>Docker connects to this runtime behind the scenes.</p>
<p>The runtime spec file is a configuration file where we give all the important things for the container to be launch like CMD, folder, network, and so on. It is the file that the container runtime uses to launch the container.</p>
<p>We can verify that Docker is using runC as its default runtime engine like this:</p>
<pre><code>$ docker info
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-35.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-use-runc-the-universal-container-runtime">How to use runC – the universal container runtime</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-36.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Because "containers" are a collection of complex and occasionally obscure system elements, they are combined into a single low-level component. This is runC.<br>As a standalone tool, infrastructure plumbers all around the world utilise runC as plumbing.</p>
<p>runC is a lightweight, portable runtime for containers. It is a command-line tool for creating and running containers according to the Open Container Initiative (OCI) specification. It has libcontainer, which is the original lower-layer library interface that the Docker engine used to set up what we call an operating system container.</p>
<p>runC is designed with the principles of security, usability at large scale, and no dependency on Docker in mind.</p>
<p>Whenever we launch a container, it starts within a second. It looks like a new OS has launched because it has all the things an OS would have (like all the commands, network card, and more). It looks like an independent OS.</p>
<h4 id="heading-how-can-a-container-be-launched-in-one-second">How can a container be launched in one second?</h4>
<p>As you may know, when you run any program, it becomes a process. So even here, every running container is a process in the host system. So whenever we launch a container, it means we have started some process.</p>
<p>It looks like that container is a different OS with its own file system, network, and so on, as I mentioned above. But the kernel runs this entire setup inside a process by using the concept of namespaces. We'll discuss namespaces further in a minute.</p>
<p>So, as the container is a process, it launches quickly.</p>
<p>A container is just a process running in the RAM. This process looks like it is running a full-flash OS inside an OS. Typically, the process (container) runs the bash command, which has an infinite lifetime till someone gives an exit command.</p>
<p>If we inspect the container, we can find the PID of the /bin/bash command running in the base OS. Now, if we kill the process of the /bin/bash, the container will also be terminated.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-37.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-what-is-a-namespace">What is a namespace?</h4>
<p>Again, running a container is also a process. But with Docker, a container is given with its own principal user, network configuration, mount folders, filesystem, and so on which they get as a common component of the baseOS. As a result, the container now has its own isolated environment, known as a namespace.</p>
<pre><code>$ lsns

# To list down all the namespace <span class="hljs-keyword">in</span> the baseOS
</code></pre><p>To launch a container, Docker Creates a Runtime Spec file for runC, and then runC uses that to launch the container. runC creates Namespaces for the container process.</p>
<p>The Image works as a Hard Drive – that is, it contains the entire file system for the container. A Container Image bundles all the data which is mounted on a storage namespace (that is, mount namespace).</p>
<p>The Image creates the entire bundle in the "/" directory of the containers. We have to unbundle it by untaring it.</p>
<p>Note that runC will not download, unzip, or untar the images for us. It can launch the container and mount the files for us to the container. For the images, we need some image management tools.</p>
<p>Also, runC only provides us with a network namespace, but Docker has to manage the network (that is, specify the IP range, provide IPs, and so on).</p>
<p>Docker can download, unzip, or unbundle the images. It can also do the required networking setup. It can also connect to the storage for us, and many such features are provided by Docker. Typically, Docker can do almost all the stuff provided by Kubernetes via its commands.</p>
<p>In Docker, we have a client-server architecture in which we have Docker CLI working as the client program and the container as the server. The server will now connect to the runtime and launch the container for us.</p>
<p>If we want to launch the container directly, we can do so with the help of runC. </p>
<p>First, we need to install runC using yum.</p>
<pre><code>$ runc list 

# This will list the available containers.
</code></pre><pre><code>$ runc spec

# To create a runtime spec file <span class="hljs-keyword">in</span> current directory.
</code></pre><p>runC is written in the Go language. So, generally it supports Go programs (images are also written in the Go language).</p>
<p><code>$ go build -o name</code> is the command to compile a Go program.</p>
<h3 id="heading-runc-commands">runC Commands:</h3>
<pre><code>$ runc create &lt;cont_name&gt;

# To launch a container (This will take config file <span class="hljs-keyword">from</span> current directory).
</code></pre><pre><code>$ runc start &lt;cont_name&gt;

# This command will run and give the output <span class="hljs-keyword">of</span> the program that we’ve specified <span class="hljs-keyword">in</span> container.
</code></pre><p>In the workspace we use the following:</p>
<pre><code>$ runc spec

# This will create a config.json file <span class="hljs-keyword">for</span> the runC configuration.
</code></pre><p>In the congif.json file, we must change the parameters-values according to our requirements. </p>
<p>For example, if we don’t want the terminal, we need to make it false. For that we can give the command to run in the arg, we can set the hostname, and so on.</p>
<p>We can connect to this container namespace by doing the following:</p>
<pre><code>$ nsenter -u -t -n &lt;pid_of_container&gt;

# To enter user and network namespace <span class="hljs-keyword">of</span> specific process ID.
</code></pre><h4 id="heading-to-create-a-container-just-by-using-runc">To create a container just by using runC:</h4>
<ol>
<li>Install runC.</li>
<li>Create config.json by running the runC spec command.</li>
<li>Mention all the important things in the above file. We also have to give process by writing its code in the Go language.</li>
<li>Create a rootfs folder in the current workspace and move the compiled Go code into this folder.</li>
<li>Now, to launch the container:</li>
</ol>
<pre><code>$ start runc create &lt;container_name&gt;
</code></pre><p>To start the container and print on the console whatever is there in the go file, run this command:</p>
<pre><code>$ runc start &lt;container_name&gt;
</code></pre><h3 id="heading-how-to-generate-an-example-specification-with-runc">How to generate an example specification with runC:</h3>
<pre><code>&gt; runc spec
&gt; cat config.json
{
  <span class="hljs-string">"ociVersion"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-string">"process"</span>: {
    <span class="hljs-string">"terminal"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-string">"user"</span>: { <span class="hljs-string">"uid"</span>: <span class="hljs-number">0</span>, <span class="hljs-string">"gid"</span>: <span class="hljs-number">0</span> },
    <span class="hljs-string">"args"</span>: [<span class="hljs-string">"sh"</span>],
    <span class="hljs-string">"env"</span>: [
      <span class="hljs-string">"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"</span>,
      <span class="hljs-string">"TERM=xterm"</span>
    ],
    <span class="hljs-string">"cwd"</span>: <span class="hljs-string">"/"</span>,
    <span class="hljs-string">"capabilities"</span>: {
      <span class="hljs-string">"bounding"</span>: [<span class="hljs-string">"CAP_AUDIT_WRITE"</span>, <span class="hljs-string">"CAP_KILL"</span>, <span class="hljs-string">"CAP_NET_BIND_SERVICE"</span>],
      [...]
    },
    <span class="hljs-string">"rlimits"</span>: [ { <span class="hljs-string">"type"</span>: <span class="hljs-string">"RLIMIT_NOFILE"</span>, <span class="hljs-string">"hard"</span>: <span class="hljs-number">1024</span>, <span class="hljs-string">"soft"</span>: <span class="hljs-number">1024</span> } ],
    <span class="hljs-string">"noNewPrivileges"</span>: <span class="hljs-literal">true</span>
  },
  <span class="hljs-string">"root"</span>: { <span class="hljs-string">"path"</span>: <span class="hljs-string">"rootfs"</span>, <span class="hljs-string">"readonly"</span>: <span class="hljs-literal">true</span> },
  <span class="hljs-string">"hostname"</span>: <span class="hljs-string">"runc"</span>,
  <span class="hljs-string">"mounts"</span>: [
    {
      <span class="hljs-string">"destination"</span>: <span class="hljs-string">"/proc"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"proc"</span>,
      <span class="hljs-string">"source"</span>: <span class="hljs-string">"proc"</span>
    },
    [...]
  ],
  <span class="hljs-string">"linux"</span>: {
    <span class="hljs-string">"resources"</span>: { <span class="hljs-string">"devices"</span>: [ { <span class="hljs-string">"allow"</span>: <span class="hljs-literal">false</span>, <span class="hljs-string">"access"</span>: <span class="hljs-string">"rwm"</span> } ] },
    <span class="hljs-string">"namespaces"</span>: [
      { <span class="hljs-string">"type"</span>: <span class="hljs-string">"pid"</span> },
      { <span class="hljs-string">"type"</span>: <span class="hljs-string">"network"</span> },
      { <span class="hljs-string">"type"</span>: <span class="hljs-string">"ipc"</span> },
      { <span class="hljs-string">"type"</span>: <span class="hljs-string">"uts"</span> },
      { <span class="hljs-string">"type"</span>: <span class="hljs-string">"mount"</span> }
    ],
    <span class="hljs-string">"maskedPaths"</span>: [
      <span class="hljs-string">"/proc/kcore"</span>,
      [...]
    ],
    <span class="hljs-string">"readonlyPaths"</span>: [
      <span class="hljs-string">"/proc/asound"</span>,
      [...]
    ]
  }
}
</code></pre><h2 id="heading-why-did-kubernetes-depreciate-docker">Why Did Kubernetes Depreciate Docker?</h2>
<p>Docker is often the first choice when it comes to managing and creating containers and images. It is extremely fast – so you might be wondering why Kubernetes dropped Docker and went on to use the CRI-O container engine? Let’s explore.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/0_jh4jUOjPPNS-bfU0.png" alt="Image" width="600" height="400" loading="lazy">
<em>Source: https://www.sumologic.com/blog/kubernetes-vs-docker/</em></p>
<p>We can check the Docker container engine like this:</p>
<pre><code>$ systemctl status docker
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-41.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here it shows Docker Application Container Engine but conntainerD is the actual engine running.</p>
<p>In Docker, when kubelet needs to connect to the ContainerD it has to go through an API Docker shim to contact runC. This acts as an interface between Docker and Kubernetes. This makes the whole process fairly complex.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-40.png" alt="Image" width="600" height="400" loading="lazy">
<em>Source: Tutorial Works</em></p>
<h3 id="heading-what-is-conatinerd">What is ConatinerD?</h3>
<p>ContainerD is an industry standard container runtime that emphasises simplicity, durability, and portability.</p>
<p>You can find a daemon-based implementation of containerD on both Linux and Windows. It is responsible for managing the whole container lifecycle of the system that it is hosted on, which includes image transfer and storage, container execution and monitoring, low-level storage, and network attachments.</p>
<h4 id="heading-features-of-containerd">Features of ContainerD:</h4>
<ul>
<li>OCI Image Spec support</li>
<li>Image push and pull support</li>
<li>Network primitives for creation, modification, and deletion of interfaces</li>
<li>Multi-tenant supported with CAS storage for global images</li>
<li>OCI Runtime Spec support (aka runC)</li>
<li>Container runtime and lifecycle support</li>
<li>Management of network namespaces containers to join existing namespaces</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-42.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-what-is-dockerd">What is dockerd?</h3>
<p>Docker's daemon may be kicked off with the help of dockerd (so you can command the daemon to manage images, containers, and so on). Dockerd is a server that runs in the background as a daemon.</p>
<p>To run the Docker daemon we can specify dockerd. After the dockerd keyword, you should supply the daemon parameters you want to use. </p>
<p>dockerd (Docker Daemon) can listen for Docker Engine API requests via three different types of Sockets: <code>unix</code>, <code>tcp</code>, and <code>fd</code>.</p>
<h3 id="heading-challenges-of-using-docker">Challenges of Using Docker</h3>
<h4 id="heading-overhead">Overhead</h4>
<p>Docker is a fairly developed platform in the container market. Along with container management, it offers many other capabilities like storage, security, and network infrastructure, among other things.</p>
<p>When compared to Cri-O and Podman, Docker's performance suffers as a direct result of the overhead caused by these additional functionalities. </p>
<p>But Kubernetes and OpenShift come equipped with all of these functions. Therefore, they want only one thing from the container engine: the ability to launch and manage the containers. In other words, they do not need any other functions.</p>
<h4 id="heading-dockershim">Dockershim</h4>
<p>In Kubernetes, the process of launching containers begins when kubelet communicates with containerD, which then contacts runC.</p>
<p>Because separate businesses are responsible for the production of containerD and kubelet, kubelet must have an additional layer in order to contact containerD (an API like layer). And inside the Docker ecosystem, this layer is referred to as Dockershim.</p>
<p>Kubernetes had depreciated dockershim because of the complexities and burden created by Docker updates.</p>
<h4 id="heading-whats-the-issue-with-dockershim">What's the issue with dockershim?</h4>
<p>Kubernetes suggested a temporary solution to include support for Docker so that it could serve as its container runtime. Dockershim's deprecation just signifies that Dockershim's code will no longer be maintained in the Kubernetes source repository. </p>
<p>Dockershim has become a significant problem for Kubernetes developers. Following this change, the Kubernetes community will only be permitted to maintain the Kubernetes Container Runtime Interface (CRI). </p>
<p>Kubernetes supports all CRI-compliant runtimes, such as containerD and CRI-O.</p>
<p>Kubernetes has come up with CRI-O as its interface to contact with runC. Kubelet will now be contacting CRI-O. Further, it will contact the runC, and the container will be launched.</p>
<p>However, like CRI-O and Docker, there are many container engines present. So, the Kubernetes community decided to create an abstraction layer on top of all container engines. So, a client can use any container engine according to their requirements.</p>
<p>This abstraction layer is called CRI (container runtime interface).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-44.png" alt="Image" width="600" height="400" loading="lazy">
<em>Kubernetes using Docker vs Kubernetes using CRI-O</em></p>
<h3 id="heading-what-is-a-cri-container-runtime-interface">What is a CRI (Container Runtime Interface)?</h3>
<p>The Kubelet program abstracts the underlying container engines. The Container Runtime Interface (CRI) is a plugin interface that lets kubelet use several container runtimes without recompilation. </p>
<p>In addition to protocol buffers, the gRPC API, and libraries, other specifications and tools are in active development for CRI.</p>
<p>Kubelet establishes a connection with CRI over the gRPC Protocol.</p>
<h3 id="heading-what-is-cri-o">What is CRI-O?</h3>
<p>CRI-OCI is an abbreviation that stands for the Container Runtime Interface and OCI, which stands for the Open Container Initiative.</p>
<p>The term CRI-O was chosen after taking into account references made by members of the CRI and CIO communities.</p>
<p>CRI-O is another container engine, but it is more lightweight than Docker since it does not include the additional capabilities that Docker has, such as networking, storage, and so on.</p>
<p>CRI-O provides a foundation that is more secure, stable, and performant for the execution of runtimes that are compatible with the Open Container Initiative (OCI). Runtimes that are OCI-compliant can be used in conjunction with the CRI-O container engine to launch containers and pods. </p>
<p>Examples of such runtimes include runC, which is the default OCI runtime, and Kata Containers. But you can use any OCI-conformant runtime in its place. </p>
<p>The goal of the CRI-O project is to replace Docker as the container engine that implements the Kubernetes Container Runtime Interface (CRI) for OpenShift Container Platform and Kubernetes.</p>
<p>The stability of CRI-O may be attributed to the fact that it is developed, tested, and distributed in tandem with major and minor versions of Kubernetes and complies with OCI standards. </p>
<p>The O's in CRI-scope are reliant on the Container Runtime Interface (CRI). The actual container engine specifications of a Kubernetes service (kubelet) were compiled and specified by CRI. </p>
<p>In light of the fact that several container engines were being developed, the CRI team decided to take this measure in order to settle Kubernetes' requirements for container engines.</p>
<p>According to the OpenShift Docs, the tools that help replace and extend what the Docker command and service provided are:</p>
<ul>
<li>crictl: For working directly with CRI-O container engines &amp; troubleshooting</li>
<li>runc: For running container images</li>
<li>podman: For managing pods and container images (run, stop, start, ps, attach, exec, and so on) outside of the container engine</li>
<li>buildah: For building, pushing and signing container images</li>
<li>skopeo: For copying, inspecting, deleting, and signing images</li>
</ul>
<h3 id="heading-cri-o-architecture">CRI-O Architecture</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-49.png" alt="Image" width="600" height="400" loading="lazy">
<em>Source: cri-o.io</em></p>
<h2 id="heading-how-to-build-a-multi-node-cluster-using-cri-o">How to Build a Multi-Node Cluster using CRI-O</h2>
<p>Following are the commands you'd use to create a multi-node Kubernetes cluster using CRI-O in Ubuntu 20.04.</p>
<p>Here's the repository that contains the set of commands: </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/gursimarh/Kubernetes-CRIO/blob/main/commands">https://github.com/gursimarh/Kubernetes-CRIO/blob/main/commands</a></div>
<pre><code>OS=xUbuntu_20<span class="hljs-number">.04</span>
VERSION=<span class="hljs-number">1.20</span>

cat &gt;&gt;<span class="hljs-regexp">/etc/</span>apt/sources.list.d/devel:kubic:libcontainers:stable.list&lt;&lt;EOF
deb https:<span class="hljs-comment">//download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /</span>
EOF

cat &gt;&gt;<span class="hljs-regexp">/etc/</span>apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list&lt;&lt;EOF
deb http:<span class="hljs-comment">//download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /</span>
EOF

curl -L https:<span class="hljs-comment">//download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -</span>

curl -L https:<span class="hljs-comment">//download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers-cri-o.gpg add -</span>

apt update

apt install -qq -y cri-o cri-o-runc cri-tools

systemctl daemon-reload

systemctl enable --now crio

curl -s https:<span class="hljs-comment">//packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -</span>


apt-add-repository <span class="hljs-string">"deb http://apt.kubernetes.io/ kubernetes-xenial main"</span>

apt install -qq -y kubeadm=<span class="hljs-number">1.20</span><span class="hljs-number">.5</span><span class="hljs-number">-00</span> kubelet=<span class="hljs-number">1.20</span><span class="hljs-number">.5</span><span class="hljs-number">-00</span> kubectl=<span class="hljs-number">1.20</span><span class="hljs-number">.5</span><span class="hljs-number">-00</span>

cat &gt;&gt;<span class="hljs-regexp">/etc/m</span>odules-load.d/crio.conf&lt;&lt;EOF
overlay
br_netfilter
EOF

modprobe overlay

modprobe br_netfilter

cat &gt;&gt;<span class="hljs-regexp">/etc/</span>sysctl.d/kubernetes.conf&lt;&lt;EOF
net.bridge.bridge-nf-call-ip6tables = <span class="hljs-number">1</span>
net.bridge.bridge-nf-call-iptables  = <span class="hljs-number">1</span>
net.ipv4.ip_forward                 = <span class="hljs-number">1</span>
EOF

sysctl --system

cat &gt;&gt;<span class="hljs-regexp">/etc/</span>crio/crio.conf.d/<span class="hljs-number">02</span>-cgroup-manager.conf&lt;&lt;EOF
[crio.runtime]
conmon_cgroup = <span class="hljs-string">"pod"</span>
cgroup_manager = <span class="hljs-string">"cgroupfs"</span>
EOF

systemctl daemon-reload

systemctl enable --now crio

systemctl restart crio

sed -i <span class="hljs-string">'/swap/d'</span> /etc/fstab

swapoff -a

systemctl disable --now ufw

kubeadm init --apiserver-advertise-address=<span class="hljs-number">172.16</span><span class="hljs-number">.16</span><span class="hljs-number">.100</span> --pod-network-cidr=<span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>/<span class="hljs-number">16</span>

kubectl --kubeconfig=<span class="hljs-regexp">/etc/</span>kubernetes/admin.conf create -f https:<span class="hljs-comment">//docs.projectcalico.org/v3.18/manifests/calico.yaml</span>

kubeadm token create --print-join-command
</code></pre><p>We can use the join command to connect the nodes to the cluster and we're ready with a multi-node cluster of Kubernetes.</p>
<h3 id="heading-heres-an-illustration-of-how-docker-kubernetes-cri-o-containerd-and-runc-work-together">Here's an illustration of how Docker, Kubernetes, CRI-O, containerD and runC work together</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-43.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-container-runtimes">Container Runtimes</h3>
<p>We have seen a lot of details on how containers work, we have defined container runtimes and how we can build our custom container using runC. Now, are there any other tools in hand for us like runC?</p>
<p>Here we will look at landscape of all the container runtimes that are available.</p>
<p>Generally, they fall into two main categories: </p>
<ol>
<li>Open Container Initiative (OCI) runtimes</li>
</ol>
<p>The OCI runtimes are further classified in two broader categories: Native Runtimes and Sandboxed &amp; Virtualized Runtimes</p>
<ol start="2">
<li>Container Runtime Interface (CRI)</li>
</ol>
<p>The CRI consists of containerD and CRI-O.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-47.png" alt="Image" width="600" height="400" loading="lazy">
<em>1. Open Container Initiative (OCI) runtimes</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/08/image-48.png" alt="Image" width="600" height="400" loading="lazy">
<em>2. Container Runtime Interface (CRI)</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Here we saw how we can build our custom images and the various tool available at our disposal. It was a long one but I hope you've enjoyed it and have learned something new.</p>
<p>I'm always open to suggestions and discussions on <a target="_blank" href="https://www.linkedin.com/in/gursimarh">LinkedIn</a>. Hit me up with direct messages.</p>
<p>If you've enjoyed my writing and want to keep me motivated, consider leaving starts on <a target="_blank" href="https://github.com/gursimarh">GitHub</a> and endorse me for relevant skills on <a target="_blank" href="https://www.linkedin.com/in/gursimarh">LinkedIn</a>.</p>
<p>Till the next one, stay safe and keep learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is Docker? Learn How to Use Containers – Explained with Examples ]]>
                </title>
                <description>
                    <![CDATA[ By Sebastian Sigl Containers are an essential tool for software development today. Running applications in any environment becomes easy when you leverage containers.  The most popular technology for running containers is Docker, which runs on any ope... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-docker-learn-how-to-use-containers-with-examples/</link>
                <guid isPermaLink="false">66d46108768263422736e8c0</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 19 Apr 2022 19:50:39 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/how-to-use-docker-containers.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sebastian Sigl</p>
<p>Containers are an essential tool for software development today. Running applications in any environment becomes easy when you leverage containers. </p>
<p>The most popular technology for running containers is <a target="_blank" href="https://www.docker.com/">Docker</a>, which runs on any operating system.</p>
<p>In this blog post, you will learn to use Docker for the top 3 most essential use cases. You will learn how to:</p>
<ul>
<li>run a database locally using Docker,</li>
<li>run automated tests using a dockerized database,</li>
<li>run your application locally and in production using Docker.</li>
</ul>
<p>You will use a Java <a target="_blank" href="https://spring.io/projects/spring-boot">Spring Boot</a> application, but all learnings apply to every other programming language of your choice.</p>
<p>To run all examples, you need to:</p>
<ul>
<li><a target="_blank" href="https://docs.docker.com/engine/install/">Install Docker</a></li>
<li><a target="_blank" href="https://www.java.com/de/download/">Install Java</a></li>
</ul>
<h2 id="heading-run-isolated-applications-using-docker">Run Isolated Applications Using Docker</h2>
<blockquote>
<p>Docker takes away repetitive, mundane configuration tasks and is used throughout the development lifecycle for fast, easy, and portable application development – desktop and cloud. (Source: <a target="_blank" href="https://www.docker.com/use-cases/">https://www.docker.com/use-cases/</a>)</p>
</blockquote>
<p>The core of Docker’s superpower is leveraging so-called <a target="_blank" href="https://en.wikipedia.org/wiki/Cgroups">cgroups</a> to create lightweight, isolated, portable, and performant environments, which you can start in seconds.</p>
<p>Let’s look at how you can use Docker to be more productive.</p>
<h2 id="heading-database-containers">Database Containers</h2>
<p>Using Docker, you can start many types of databases in seconds. It’s easy, and it does not pollute your local system with other requirements you need to run the database. Everything comes packaged with the Docker container.</p>
<p>By searching <a target="_blank" href="https://hub.docker.com/">hub.docker.com</a>, you can find ready-to-use containers for many databases. </p>
<p>Using the <code>docker run</code> command, you can start a <a target="_blank" href="https://hub.docker.com/_/mysql/">MySQL Docker container</a>.</p>
<pre><code class="lang-sh">docker run --rm -v <span class="hljs-string">"<span class="hljs-variable">$PWD</span>/data"</span>:/var/lib/mysql --name mysql -e MYSQL_ROOT_PASSWORD=admin-password -e MYSQL_DATABASE=my-database -p 3306:3306 mysql:8.0.28-debian
</code></pre>
<p>This command uses advanced features of running a Docker container:</p>
<ul>
<li><code>-v "$PWD/data"</code> maps your local directory <code>./data</code> to the docker container, which allows you to start the Docker container without losing your data,</li>
<li><code>-p 3306:3306</code> maps the container port <code>3306</code> to our machine so that other applications can use it,</li>
<li><code>-e MYSQL_DATABASE=my-database</code> sets an environment variable to create a new database called <code>my-database</code> automatically,</li>
<li><code>-e MYSQL_ROOT_PASSWORD=admin-password</code> sets an environment variable to set the admin password,</li>
<li><code>--rm</code> removes the container when stopped.</li>
</ul>
<p>These environment variables and more are documented on the <a target="_blank" href="https://hub.docker.com/_/mysql/?tab=description">Docker image’s page</a>.</p>
<h3 id="heading-how-to-use-database-containers-for-development">How to Use Database Containers For Development</h3>
<p>You will use a popular tech stack to build a web application based on <a target="_blank" href="https://www.w3schools.com/java/java_intro.asp">Java</a> and <a target="_blank" href="https://spring.io/projects/spring-boot">Spring Boot</a>. To focus on the Docker parts, you can clone a simple demo application from the official <a target="_blank" href="https://spring.io/guides/gs/accessing-data-rest/">Accessing JPA Data With Rest Guide</a>.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Download the sample application</span>
git <span class="hljs-built_in">clone</span> https://github.com/spring-guides/gs-accessing-data-rest.git

<span class="hljs-comment"># Open the final application folder</span>
<span class="hljs-built_in">cd</span> complete
</code></pre>
<p>The application comes with an in-memory database, which is not valuable for production because it does not allow multiple services to access and mutate a single database. A <a target="_blank" href="https://www.mysql.com/">MySQL</a> Database is more suitable for scaling your application to many more reads and writes.</p>
<p>Therefore, add the MySQL driver to your <code>pom.xml</code>:</p>
<pre><code class="lang-xml">       <span class="hljs-comment">&lt;!-- Disable in memory database --&gt;</span>
       <span class="hljs-comment">&lt;!--
       &lt;dependency&gt;
           &lt;groupId&gt;com.h2database&lt;/groupId&gt;
           &lt;artifactId&gt;h2&lt;/artifactId&gt;
           &lt;scope&gt;runtime&lt;/scope&gt;
       &lt;/dependency&gt;
       --&gt;</span>

       <span class="hljs-comment">&lt;!-- MySQL driver --&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>mysql<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mysql-connector-java<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>runtime<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<p>Now, you need to add the configuration to connect to your database by adding a configuration file <code>src/main/resources/application.properties</code>.</p>
<pre><code class="lang-properties"># Database configuration
spring.datasource.url=jdbc:mysql://localhost:3306/my-database
spring.datasource.username=root
spring.datasource.password=admin-password

# Create table and database automatically
spring.jpa.hibernate.ddl-auto=update
</code></pre>
<p>You can now start the application and call existing endpoints:</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Get all people</span>
curl http://localhost:8080/people

<span class="hljs-comment"># Add a person</span>
curl -i -H <span class="hljs-string">"Content-Type:application/json"</span> -d <span class="hljs-string">'{"firstName": "Frodo", "lastName": "Baggins"}'</span> http://localhost:8080/people

<span class="hljs-comment"># Get all people again, which now returns the created person</span>
curl http://localhost:8080/people
</code></pre>
<p>You successfully used your rudimentary application, which writes and reads data in your database. Using the MySQL Docker database gives you a robust database up in seconds, and you can use it from any application.</p>
<h3 id="heading-how-to-use-database-containers-for-integration-tests">How to Use Database Containers for Integration Tests</h3>
<p>The application already has tests that expect a running database. But, because you replaced your in-memory database with an actual MySQL database, tests won’t run successfully if you stop your database.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># Stop database</span>
docker rm -f mysql

<span class="hljs-comment"># Run tests</span>
./mvnw clean <span class="hljs-built_in">test</span>

... skipped output ...
[ERROR] Tests run: 7, Failures: 0, Errors: 7, Skipped: 0
... skipped output ...
</code></pre>
<p>To quickly start and stop containers running tests, there is a handy tool called <a target="_blank" href="https://github.com/testcontainers">testcontainers</a>. There you'll find libraries for many programming languages, including Java.</p>
<p>First, you need to add some dependencies to your <code>pom.xml</code>:</p>
<pre><code class="lang-xml">       <span class="hljs-comment">&lt;!-- testcontainer --&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.testcontainers<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>testcontainers<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.16.3<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.testcontainers<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>mysql<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.16.3<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.testcontainers<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>junit-jupiter<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.16.3<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
</code></pre>
<p>You need to update the tests to make use of the testcontainers, which starts the database on every test run. Add an annotation and a field to the test to make use of it:</p>
<pre><code class="lang-java"><span class="hljs-comment">//added imports</span>
<span class="hljs-keyword">import</span> org.springframework.boot.test.context.SpringBootTest;
<span class="hljs-keyword">import</span> org.springframework.test.context.DynamicPropertyRegistry;
<span class="hljs-keyword">import</span> org.springframework.test.context.DynamicPropertySource;
<span class="hljs-keyword">import</span> org.testcontainers.containers.MySQLContainer;
<span class="hljs-keyword">import</span> org.testcontainers.junit.jupiter.Container;
<span class="hljs-keyword">import</span> org.testcontainers.junit.jupiter.Testcontainers;

<span class="hljs-meta">@SpringBootTest</span>
<span class="hljs-meta">@AutoConfigureMockMvc</span>
<span class="hljs-meta">@Testcontainers</span> <span class="hljs-comment">// Annotation to enable testcontainers</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AccessingDataRestApplicationTests</span> </span>{

   <span class="hljs-comment">// Field to access the started database</span>
   <span class="hljs-meta">@Container</span>
   <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> MySQLContainer database = <span class="hljs-keyword">new</span> MySQLContainer&lt;&gt;(<span class="hljs-string">"mysql:5.7.34"</span>);

   <span class="hljs-comment">//Set database configuration using the started database</span>
   <span class="hljs-meta">@DynamicPropertySource</span>
   <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">databaseProperties</span><span class="hljs-params">(DynamicPropertyRegistry registry)</span> </span>{
       registry.add(<span class="hljs-string">"spring.datasource.url"</span>, database::getJdbcUrl);
       registry.add(<span class="hljs-string">"spring.datasource.username"</span>, database::getUsername);
       registry.add(<span class="hljs-string">"spring.datasource.password"</span>, database::getPassword);
   }
</code></pre>
<p>For each test execution, the database is started for you, which allows you to use an actual database when you execute tests. All the wiring, setting it up, startup and cleanup are all done for you.</p>
<h2 id="heading-dockerize-your-application">Dockerize Your Application</h2>
<p>Dockerizing your application using simple Docker tools is possible but not recommended. </p>
<p>You can build your application, use a base container that contains Java and copy and run your application. But there are a lot of pitfalls, and this is the case for every language and framework. So always look for tools that make your life easier. </p>
<p>In this example, you will use <a target="_blank" href="https://github.com/GoogleContainerTools/jib">Jib</a> and <a target="_blank" href="https://github.com/GoogleContainerTools/distroless">distroless containers</a> to build a Docker container easily. Using both in combination gives you a minimal, secure, and reproducible container, which works the same way locally and in production. </p>
<p>To use Jib, you need to add it as a maven plugin by adding it to your <code>pom.xml</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
               <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
               <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
           <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>

        <span class="hljs-comment">&lt;!-- Jib plugin --&gt;</span>
           <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
               <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.google.cloud.tools<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
               <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>jib-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
               <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.2.1<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
               <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                   <span class="hljs-tag">&lt;<span class="hljs-name">from</span>&gt;</span>
                       <span class="hljs-tag">&lt;<span class="hljs-name">image</span>&gt;</span>gcr.io/distroless/java17:nonroot<span class="hljs-tag">&lt;/<span class="hljs-name">image</span>&gt;</span>
                   <span class="hljs-tag">&lt;/<span class="hljs-name">from</span>&gt;</span>
                   <span class="hljs-tag">&lt;<span class="hljs-name">to</span>&gt;</span>
                       <span class="hljs-tag">&lt;<span class="hljs-name">image</span>&gt;</span>my-docker-image<span class="hljs-tag">&lt;/<span class="hljs-name">image</span>&gt;</span>
                   <span class="hljs-tag">&lt;/<span class="hljs-name">to</span>&gt;</span>
               <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
           <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>
</code></pre>
<p>You can now build the image and run the application:</p>
<pre><code class="lang-sh"><span class="hljs-comment"># build the docker container</span>
./mvnw compile jib:dockerBuild

<span class="hljs-comment"># find your build image</span>
docker images

<span class="hljs-comment"># start the database</span>
docker run --rm -v <span class="hljs-string">"<span class="hljs-variable">$PWD</span>/data"</span>:/var/lib/mysql --name mysql -e MYSQL_ROOT_PASSWORD=admin-password -e MYSQL_DATABASE=my-database -p 3306:3306 mysql:8.0.28-debian


<span class="hljs-comment"># start the docker container which contains your application</span>
docker run --net=host my-docker-image

… skipped output…
2022-04-15 17:43:51.509  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path <span class="hljs-string">''</span>
2022-04-15 17:43:51.521  INFO 1 --- [           main] c.e.a.AccessingDataRestApplication       : Started AccessingDataRestApplication <span class="hljs-keyword">in</span> 6.146 seconds (JVM running <span class="hljs-keyword">for</span> 6.568)
</code></pre>
<p>The application is started with network mode host <code>--net=host</code>, which makes it easy to just connect to the database you started. Alternatively, you can create a <code>docker network</code> and start both in the same network.</p>
<p>You can push your container to a container registry and reference it from any container orchestration tool to run your application in production.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, you learned how to leverage Docker to create, test. and run applications without polluting your system. </p>
<p>Everything is in your isolated Docker environment and works locally, as on continuous integration systems and production systems where you might start hundreds of your applications.</p>
<p>You find the ready to use example application in <a target="_blank" href="https://github.com/sesigl/docker-for-development-example-application">my GitHub Docker For Development Example Application Repository</a>.</p>
<p>I hope you enjoyed the article.</p>
<p>If you liked it and felt the need to give me a round of applause or just want to get in touch, <a target="_blank" href="https://twitter.com/sesigl">follow me on Twitter</a>.</p>
<p>I work at eBay Kleinanzeigen, one of the world’s biggest classified companies. By the way, <a target="_blank" href="https://www.ebay-kleinanzeigen.de/careers">we are hiring</a>!</p>
<h2 id="heading-references">References</h2>
<ul>
<li><a target="_blank" href="https://hub.docker.com/_/mysql/">Docker Hub: MySQL Image</a></li>
<li><a target="_blank" href="https://docs.docker.com/engine/reference/commandline/run/">Docker documentation: run command</a></li>
<li><a target="_blank" href="https://code.visualstudio.com/docs/remote/containers">Visual Studio Code: Dev Containers</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/learn-java-free-java-courses-for-beginners/">Learn Java – free Java courses</a></li>
<li><a target="_blank" href="https://www.youtube.com/watch?v=H6gR_Cv4yWI">Youtube: Build containers faster with Jib</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Kubernetes Security – How to Use Dynamic Admission Control to Secure Your Container Supply Chain ]]>
                </title>
                <description>
                    <![CDATA[ By Nahla Davies Containers have exploded in popularity in recent years. And as more developers are using these containers, they need more tools to manage them and their interactions with them effectively.  To help manage all this, many devs use Kuber... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/kubernetes-security-dynamic-admission-control/</link>
                <guid isPermaLink="false">66d460497df3a1f32ee7f877</guid>
                
                    <category>
                        <![CDATA[ Application Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ container ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 05 Aug 2021 16:37:56 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/08/pexels-pixabay-39624.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Nahla Davies</p>
<p>Containers have exploded in popularity in recent years. And as more developers are using these containers, they need more tools to manage them and their interactions with them effectively. </p>
<p>To help manage all this, many devs use Kubernetes. And it has <a target="_blank" href="https://www.freecodecamp.org/news/the-kubernetes-handbook/">become the de facto standard</a> for container orchestration.</p>
<p>While containers help make the software development and deployment lifecycle more efficient, they can also expand the possible ways hackers can attack your organization. </p>
<p>And while Kubernetes definitely simplifies the process of managing containers, it too has security vulnerabilities. </p>
<p>Given the popularity of Kubernetes, cybercriminals have put a great deal of effort into exploiting those vulnerabilities. As a result, attacks on the supply chain <a target="_blank" href="https://www.darkreading.com/cloud/software-container-supply-chain-sees-spike-in-attacks/d/d-id/1341353">have risen significantly</a> over the last year.</p>
<p>And so securing the Kubernetes supply chain is a high priority for many organizations. </p>
<p>One important security feature that you should pay close attention to if you're a Kubernetes user is dynamic admission control. </p>
<p>In this article, we'll discuss Kubernetes supply chain vulnerabilities and how to address them with dynamic admission control.</p>
<h2 id="heading-vulnerabilities-in-the-kubernetes-supply-chain">Vulnerabilities in the Kubernetes Supply Chain</h2>
<p>Recently, nearly all Kubernetes users <a target="_blank" href="https://www.redhat.com/rhdc/managed-files/cl-state-kubernetes-security-report-ebook-f29117-202106-en.pdf">experienced a security incident</a> caused by various vulnerabilities, ranging from misconfiguration to failed audits. Because of this, security concerns have started to take their toll on deployment practices. </p>
<p>More than half of organizations that use Kubernetes have delayed deploying an application solely based on security issues.</p>
<p>If you want to secure your Kubernetes supply chain, you should have an understanding of the supply chain components for containerized applications and their associated vulnerabilities. </p>
<p>The supply chain extends well beyond Kubernetes itself and <a target="_blank" href="https://www.freecodecamp.org/news/a-simple-introduction-to-kubernetes-container-orchestration/">includes the contents of the containers</a> that Kubernetes manages as well as the container host. </p>
<p>Within the container, you'll typically have a bunch of code from a variety of sources (both internal and external). This gives attackers numerous opportunities to get creative. </p>
<p>To protect against these threats, you need to properly secure all code sets regardless of their source – and this can be challenging. </p>
<p>For example, securing code sourced from Linux distributions such as openssl libraries or glibc may only require you to apply the most recent patch. </p>
<p>But code from other external sources, such as upstream open-source libraries or internal development processes, can be more difficult to secure.</p>
<p>Internal development is perhaps the biggest organizational threat, particularly when developers prioritize speed of release over security.  </p>
<p>Many organizations have decentralized responsibility for container security, with everyone from developers to DevOps getting involved. But more organizations <a target="_blank" href="https://devops.com/from-agile-to-devops-to-devsecops-the-next-evolution/">are building DevSecOps units</a> and placing Kubernetes security in their hands.</p>
<h2 id="heading-how-to-secure-the-kubernetes-supply-chain-with-dynamic-admission-control">How to Secure the Kubernetes Supply Chain with Dynamic Admission Control</h2>
<p>In Kubernetes, dynamic admission control involves user-defined or configured admission controllers rather than standard built-in controllers.</p>
<h3 id="heading-what-are-admission-controllers">What are admission controllers?</h3>
<p>Admission controllers take over after the Kubernetes API server receives an authentication and authorization of a request. These pieces of code intercept the request before a container gets initialized and is added as a pod to the Kubernetes cluster. </p>
<p>Essentially, the admission controller attempts to verify that the image is safe. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/image-12.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/">Image Source</a></em></p>
<p>Admission controllers <a target="_blank" href="https://www.openpolicyagent.org/docs/v0.11.0/kubernetes-admission-control/">implement a variety of functions</a>, from enforcing resource quotas to running cluster-critical tasks. You'll need to have properly configured admission controllers to properly operate many of Kubernetes' advanced features.</p>
<h3 id="heading-what-are-admission-webhooks">What are admission webhooks?</h3>
<p>Dynamic admission controllers rely on admission webhooks, which are user-defined HTTP callbacks that process admission requests. </p>
<p>In Kubernetes, there are two types of admission webhooks: validating admission webhooks and mutating admission webhooks. </p>
<p>In the admission control process, mutating admission controls run before validating controls. Both types of webhooks are essentially self-explanatory in principle, although their specific operation requires some explanation. </p>
<h4 id="heading-validating-admission-webhooks">Validating admission webhooks</h4>
<p>Validating admission webhooks intercept and validate requests to the Kubernetes API using an external webhook. Importantly, though, they can not mutate requests. </p>
<p>All validating webhooks matching a request run in parallel (because no potential modification can occur), and the controller rejects the request on the failure of any matching webhook. </p>
<p>Validation admission webhooks are all-or-nothing – if the request fails to match precisely, the control rejects it.</p>
<h4 id="heading-mutating-admission-webhooks">Mutating admission webhooks</h4>
<p>In contrast, mutating admission webhooks are able to modify requests, allowing requests that are only slightly non-compliant with the rule to process. </p>
<p>If multiple webhooks match a request, they run serially, and each may modify the request. Because mutating controllers run first, mutated requests can still be rejected by validating webhooks.</p>
<p>The joint operation of mutating and validating admission webhooks allows Kubernetes developers to <a target="_blank" href="https://www.freecodecamp.org/news/how-to-become-a-certified-kubernetes-application-developer/">ensure that requests are compliant</a> and valid before instantiation of containers.</p>
<h2 id="heading-kubernetes-pod-security-policies">Kubernetes Pod Security Policies</h2>
<p>Pod security policies (or PSPs) are a Kubernetes security feature that relies on the implementation of admission controls. </p>
<p>PSPs set conditions and defaults that Kubernetes pods must meet in order to be accepted into the container system. </p>
<p>PSPs can enforce such policies as disabling privileged containers, preventing privilege escalation, and preventing containers from running as root. </p>
<p>PSPs allow admins to enforce organizational security policies across an entire namespace easily. While PSPs require enabling admission controllers, they must be separately enabled.</p>
<p>Under the Kubernetes Pod Security Standards, there are three types of policies:</p>
<ul>
<li>The Baseline Policy has minimal restrictions, although it does not allow privilege escalation. </li>
<li>For the lowest trust users, there is the Restricted Policy which disables certain functions in accordance with pod hardening best practices. </li>
<li>At the highest level is the Privileged Policy, which is the broadest, allowing the most permissions and privilege escalation. </li>
</ul>
<p>Kubernetes started deprecating PSPs with the release of version 1.21. PSPs will be <a target="_blank" href="https://kubernetes.io/blog/2021/04/06/podsecuritypolicy-deprecation-past-present-and-future/">removed entirely in 2022</a> with the release of version 1.25. As a result, if you use Kubernetes you should carefully consider alternative security options for all future container applications.</p>
<h2 id="heading-dont-neglect-standard-kubernetes-security-tools">Don’t Neglect Standard Kubernetes Security Tools</h2>
<p>If you use containers, you should be aware of common vulnerabilities throughout your Kubernetes environment. </p>
<p>To achieve maximum security, both developers and users must apply Kubernetes-specific security features such as dynamic admission control and standard network security features <a target="_blank" href="https://www.freecodecamp.org/news/what-does-a-vpn-do-and-how-does-it-work-a-guide-to-virtual-private-networks/">like VPNs</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/image-13.png" alt="Image" width="600" height="400" loading="lazy">
<em><a target="_blank" href="https://securityboulevard.com/2020/03/vpn-a-key-to-securing-an-online-work-environment/">Image Source</a></em></p>
<p>A recent Kubernetes vulnerability involved attackers who had access to the API and who could obtain complete administrator access to a Kubernetes cluster and all associated resources. </p>
<p>A VPN can help avoid this and other similar vulnerabilities where the API server is exposed. But it's important to select the right VPN for your needs.</p>
<h3 id="heading-how-to-choose-a-vpn">How to Choose a VPN</h3>
<p>According to cybersecurity expert Ludovic Rembert of Privacy Canada, encryption protocols <a target="_blank" href="https://privacycanada.net/best-vpn/#:~:text=a%20vpn%20protocol">are the most important factor</a> to look for in a VPN. </p>
<blockquote>
<p>“A VPN protocol determines how your data gets routed between your machine and the server. Different protocols have different costs and benefits depending on what you need. For example, some prioritize privacy and security, while others prioritize speed….a PE Provider Edge device is a single device, or multiple devices, at the edge of the provider’s network. This device then connects through Consumer Edge devices. In this setup, users can view a website, while the provider device is only aware of the VPN device.” – Rembert</p>
</blockquote>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Containerized applications will continue to become more widely used in coming years, as will container management resources <a target="_blank" href="https://www.freecodecamp.org/news/learn-kubernetes-in-under-3-hours-a-detailed-guide-to-orchestrating-containers-114ff420e882/">such as Kubernetes</a>. </p>
<p>As the popularity of these tools increases, the number of attacks at all points in the supply chain will also increase. </p>
<p>So if you work with Kubernetes, you need to take advantage of every security resource available to ensure maximum application security and reliability. </p>
<p>And applying dynamic application controls to verify that the requests are compliant with security policies is one important step in this process.  </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Docker Handbook – Learn Docker for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ The concept of containerization itself is pretty old. But the emergence of the Docker Engine in 2013 has made it much easier to containerize your applications. According to the Stack Overflow Developer Survey - 2020, Docker is the #1 most wanted plat... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-docker-handbook/</link>
                <guid isPermaLink="false">66b0ab4d8d675d0da5f1ab81</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Farhan Hasin Chowdhury ]]>
                </dc:creator>
                <pubDate>Mon, 01 Feb 2021 16:35:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/The-Docker-Handbook-Mockup.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The concept of containerization itself is pretty old. But the emergence of the <a target="_blank" href="https://docs.docker.com/get-started/overview/#docker-engine">Docker Engine</a> in 2013 has made it much easier to containerize your applications.</p>
<p>According to the <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#overview">Stack Overflow Developer Survey - 2020</a>, <a target="_blank" href="https://docker.com/">Docker</a> is the <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-wanted5">#1 most wanted platform</a>, <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-loved5">#2 most loved platform</a>, and also the <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#technology-platforms">#3 most popular platform</a>.</p>
<p>As in-demand as it may be, getting started can seem a bit intimidating at first. So in this book, we'll be learning everything from the basics to a more intermediate level of containerization. After going through the entire book, you should be able to:</p>
<ul>
<li>Containerize (almost) any application</li>
<li>Upload custom Docker Images to online registries</li>
<li>Work with multiple containers using Docker Compose</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>Familiarity with the Linux Terminal</li>
<li>Familiarity with JavaScript (some later projects use JavaScript)</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-introduction-to-containerization-and-docker">Introduction to Containerization and Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-docker">How to Install Docker</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-install-docker-on-macos">How to Install Docker on macOS</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-docker-on-windows">How to Install Docker on Windows</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-docker-on-linux">How to Install Docker on Linux</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-hello-world-in-docker-intro-to-docker-basics">Hello World in Docker - Intro to Docker Basics</a><ul>
<li><a class="post-section-overview" href="#heading-what-is-a-container">What is a Container?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-a-docker-image">What is a Docker Image?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-a-docker-registry">What is a Docker Registry?</a></li>
<li><a class="post-section-overview" href="#heading-docker-architecture-overview">Docker Architecture Overview</a></li>
<li><a class="post-section-overview" href="#heading-the-full-picture">The Full Picture</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-docker-container-manipulation-basics">Docker Container Manipulation Basics</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-run-a-container">How to Run a Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-publish-a-port">How to Publish a Port</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-detached-mode">How to Use Detached Mode</a></li>
<li><a class="post-section-overview" href="#heading-how-to-list-containers">How to List Containers</a></li>
<li><a class="post-section-overview" href="#heading-how-to-name-or-rename-a-container">How to Name or Rename a Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-stop-or-kill-a-running-container">How to Stop or Kill a Running Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-restart-a-container">How to Restart a Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-container-without-running">How to Create a Container Without Running</a></li>
<li><a class="post-section-overview" href="#heading-how-to-remove-dangling-containers">How to Remove Dangling Containers</a></li>
<li><a class="post-section-overview" href="#heading-how-to-run-a-container-in-interactive-mode">How to Run a Container in Interactive Mode</a></li>
<li><a class="post-section-overview" href="#heading-how-to-execute-commands-inside-a-container">How to Execute Commands Inside a Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-executable-images">How to Work With Executable Images</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-docker-image-manipulation-basics">Docker Image Manipulation Basics</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-create-a-docker-image">How to Create a Docker Image</a></li>
<li><a class="post-section-overview" href="#heading-how-to-tag-docker-images">How to Tag Docker Images</a></li>
<li><a class="post-section-overview" href="#heading-how-to-list-and-remove-docker-images">How to List and Remove Docker Images</a></li>
<li><a class="post-section-overview" href="#heading-how-to-understand-the-many-layers-of-a-docker-image">How to Understand the Many Layers of a Docker Image</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-nginx-from-source">How to Build NGINX from Source</a></li>
<li><a class="post-section-overview" href="#heading-how-to-optimize-docker-images">How to Optimize Docker Images</a></li>
<li><a class="post-section-overview" href="#heading-embracing-alpine-linux">Embracing Alpine Linux</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-executable-docker-images">How to Create Executable Docker Images</a></li>
<li><a class="post-section-overview" href="#heading-how-to-share-your-docker-images-online">How to Share Your Docker Images Online</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-containerize-a-javascript-application">How to Containerize a JavaScript Application</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-write-the-development-dockerfile">How to Write the Development Dockerfile</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-bind-mounts-in-docker">How to Work With Bind Mounts in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-anonymous-volumes-in-docker">How to Work With Anonymous Volumes in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-perform-multi-staged-builds-in-docker">How to Perform Multi-Staged Builds in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-ignore-unnecessary-files">How to Ignore Unnecessary Files</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-network-manipulation-basics-in-docker">Network Manipulation Basics in Docker</a><ul>
<li><a class="post-section-overview" href="#heading-docker-network-basics">Docker Network Basics</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-user-defined-bridge-in-docker">How to Create a User-Defined Bridge in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-attach-a-container-to-a-network-in-docker">How to Attach a Container to a Network in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-detach-containers-from-a-network-in-docker">How to Detach Containers from a Network in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-get-rid-of-networks-in-docker">How to Get Rid of Networks in Docker</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-containerize-a-multi-container-javascript-application">How to Containerize a Multi-Container JavaScript Application</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-run-the-database-server">How to Run the Database Server</a></li>
<li><a class="post-section-overview" href="#heading-how-to-work-with-named-volumes-in-docker">How to Work with Named Volumes in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-access-logs-from-a-container-in-docker">How to Access Logs from a Container in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-network-and-attaching-the-database-server-in-docker">How to Create a Network and Attaching the Database Server in Docker</a></li>
<li><a class="post-section-overview" href="#heading-how-to-write-the-dockerfile">How to Write the Dockerfile</a></li>
<li><a class="post-section-overview" href="#heading-how-to-execute-commands-in-a-running-container">How to Execute Commands in a Running Container</a></li>
<li><a class="post-section-overview" href="#heading-how-to-write-management-scripts-in-docker">How to Write Management Scripts in Docker</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-compose-projects-using-docker-compose">How to Compose Projects Using Docker-Compose</a><ul>
<li><a class="post-section-overview" href="#heading-docker-compose-basics">Docker Compose Basics</a></li>
<li><a class="post-section-overview" href="#heading-how-to-start-services-in-docker-compose">How to Start Services in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-list-services-in-docker-compose">How to List Services in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-execute-commands-inside-a-running-service-in-docker-compose">How to Execute Commands Inside a Running Service in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-access-logs-from-a-running-service-in-docker-compose">How to Access Logs from a Running Service in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-stop-services-in-docker-compose">How to Stop Services in Docker Compose</a></li>
<li><a class="post-section-overview" href="#heading-how-to-compose-a-full-stack-application-in-docker-compose">How to Compose a Full-stack Application in Docker Compose</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-project-code">Project Code</h2>
<p>Code for the example projects can be found in the following repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/fhsinchy/docker-handbook-projects/">https://github.com/fhsinchy/docker-handbook-projects/</a></div>
<p>You can find the complete code in the <code>completed</code> branch.</p>
<h2 id="heading-contributions">Contributions</h2>
<p>This book is completely open-source and quality contributions are more than welcome. You can find the full content in the following repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/fhsinchy/the-docker-handbook">https://github.com/fhsinchy/the-docker-handbook</a></div>
<p>I usually do my changes and updates on the GitBook version of the book first and then publish them on freeCodeCamp. You can find the always updated and often unstable version of the book at the following link:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docker-handbook.farhan.dev/">https://docker-handbook.farhan.dev/</a></div>
<p>If you're looking for a frozen but stable version of the book, then freeCodeCamp will be the best place to go:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.freecodecamp.org/news/the-docker-handbook/">https://www.freecodecamp.org/news/the-docker-handbook/</a></div>
<p>Whichever version of the book you end up reading though, don't forget to let me know your opinion. Constructive criticism is always welcomed.</p>
<h2 id="heading-introduction-to-containerization-and-docker">Introduction to Containerization and Docker</h2>
<p>According to <a target="_blank" href="https://www.ibm.com/cloud/learn/containerization#toc-what-is-co-r25Smlqq">IBM</a>, </p>
<blockquote>
<p>Containerization involves encapsulating or packaging up software code and all its dependencies so that it can run uniformly and consistently on any infrastructure.</p>
</blockquote>
<p>‌In other words, containerization lets you bundle up your software along with all its dependencies in a self-contained package so that it can be run without going through a troublesome setup process.</p>
<p>‌Let's consider a real life scenario here. Assume you have developed an awesome book management application that can store information regarding all the books you own, and can also serve the purpose of a book lending system for your friends.</p>
<p>‌If you make a list of the dependencies, that list may look as follows:</p>
<ul>
<li>Node.js</li>
<li>Express.js</li>
<li>SQLite3</li>
</ul>
<p>Well, theoretically this should be it. But practically there are some other things as well. Turns out <a target="_blank" href="https://nodejs.org/">Node.js</a> uses a build tool known as <code>node-gyp</code> for building native add-ons. And according to the <a target="_blank" href="https://github.com/nodejs/node-gyp#installation">installation instruction</a> in the <a target="_blank" href="https://github.com/nodejs/node-gyp">official repository</a>, this build tool requires Python 2 or 3 and a proper C/C++ compiler tool-chain. </p>
<p>Taking all these into account, the final list of dependencies is as follows:</p>
<ul>
<li>Node.js</li>
<li>Express.js</li>
<li>SQLite3</li>
<li>Python 2 or 3</li>
<li>C/C++ tool-chain</li>
</ul>
<p>Installing Python 2 or 3 is pretty straightforward regardless of the platform you're on. Setting up the C/C++ tool-chain is pretty easy on Linux, but on Windows and Mac it's a painful task. </p>
<p>On Windows, the C++ build tools package measures at gigabytes and takes quite some time to install. On a Mac, you can either install the gigantic <a target="_blank" href="https://developer.apple.com/xcode/">Xcode</a> application or the much smaller <a target="_blank" href="https://developer.apple.com/downloads/">Command Line Tools for Xcode</a> package. </p>
<p>Regardless of the one you install, it still may break on OS updates. In fact, the problem is so prevalent that there are <a target="_blank" href="https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md">Installation notes for macOS Catalina</a> available on the official repository.</p>
<p>Let's assume that you've gone through all the hassle of setting up the dependencies and have started working on the project. Does that mean you're out of danger now? Of course not.</p>
<p>What if you have a teammate who uses Windows while you're using Linux. Now you have to consider the inconsistencies of how these two different operating systems handle paths. Or the fact that popular technologies like <a target="_blank" href="https://nginx.org/">nginx</a> are not well optimized to run on Windows. Some technologies like <a target="_blank" href="https://redis.io/">Redis</a> don't even come pre-built for Windows.</p>
<p>Even if you get through the entire development phase, what if the person responsible for managing the servers follows the wrong deployment procedure?</p>
<p>All these issues can be solved if only you could somehow:</p>
<ul>
<li>Develop and run the application inside an isolated environment (known as a container) that matches your final deployment environment.</li>
<li>Put your application inside a single file (known as an image) along with all its dependencies and necessary deployment configurations.</li>
<li>And share that image through a central server (known as a registry) that is accessible by anyone with proper authorization.</li>
</ul>
<p>Your teammates will then be able to download the image from the registry, run the application as it is within an isolated environment free from the platform specific inconsistencies, or even deploy directly on a server, since the image comes with all the proper production configurations.</p>
<p>That is the idea behind containerization: putting your applications inside a self-contained package, making it portable and reproducible across various environments.</p>
<p><strong>Now the question is "What role does Docker play here?"</strong></p>
<p>As I've already explained, containerization is an idea that solves a myriad of problems in software development by putting things into boxes. </p>
<p>This very idea has quite a few implementations. <a target="_blank" href="https://www.docker.com/">Docker</a> is such an implementation. It's an open-source containerization platform that allows you to containerize your applications, share them using public or private registries, and also to <a target="_blank" href="https://docs.docker.com/get-started/orchestration/">orchestrate</a> them.</p>
<p>Now, Docker is not the only containerization tool on the market, it's just the most popular one. Another containerization engine that I love is called <a target="_blank" href="https://podman.io/">Podman</a> developed by Red Hat. Other tools like <a target="_blank" href="https://github.com/GoogleContainerTools/kaniko">Kaniko</a> by Google, <a target="_blank" href="https://coreos.com/rkt/">rkt</a> by CoreOS are amazing, but they're not ready to be a drop-in replacement for Docker just yet.</p>
<p>Also, if you want a history lesson, you may read the amazing <a target="_blank" href="https://blog.aquasec.com/a-brief-history-of-containers-from-1970s-chroot-to-docker-2016">A Brief History of Containers: From the 1970s Till Now</a> which covers most of the major turning points for the technology.</p>
<h2 id="heading-how-to-install-docker">How to Install Docker</h2>
<p>Installation of Docker varies greatly depending on the operating system you’re using. But it's universally simple across the board. </p>
<p>Docker runs flawlessly on all three major platforms, Mac, Windows, and Linux. Among the three, the installation process on Mac is the easiest, so we'll start there.</p>
<h3 id="heading-how-to-install-docker-on-macos">How to Install Docker on macOS</h3>
<p>On a mac, all you have to do is navigate to the official <a target="_blank" href="https://www.docker.com/products/docker-desktop">download page</a> and click the <em>Download for Mac (stable)</em> button. </p>
<p>You’ll get a regular looking <em>Apple Disk Image</em> file and inside the file, there will be the application. All you have to do is drag the file and drop it in your Applications directory.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/drag-docker-in-applications-directory.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can start Docker by simply double-clicking the application icon. Once the application starts, you'll see the Docker icon appear on your menu-bar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-icon-in-menubar.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, open up the terminal and execute <code>docker --version</code> and <code>docker-compose --version</code> to ensure the success of the installation.</p>
<h3 id="heading-how-to-install-docker-on-windows">How to Install Docker on Windows</h3>
<p>On Windows, the procedure is almost the same, except there are a few extra steps that you’ll need to go through. The installation steps are as follows:</p>
<ol>
<li>Navigate to <a target="_blank" href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">this site</a> and follow the instructions for installing WSL2 on Windows 10.</li>
<li>Then navigate to the official <a target="_blank" href="https://www.docker.com/products/docker-desktop">download page</a> and click the <em>Download for Windows (stable)</em> button.</li>
<li>Double-click the downloaded installer and go through the installation with the defaults.</li>
</ol>
<p>Once the installation is done, start <em>Docker Desktop</em> either from the start menu or your desktop. The docker icon should show up on your taskbar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-icon-in-taskbar.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, open up Ubuntu or whatever distribution you've installed from Microsoft Store. Execute the <code>docker --version</code> and <code>docker-compose --version</code> commands to make sure that the installation was successful.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-and-compose-version-on-windows.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can access Docker from your regular Command Prompt or PowerShell as well. It's just that I prefer using WSL2 over any other command line on Windows.</p>
<h3 id="heading-how-to-install-docker-on-linux">How to Install Docker on Linux</h3>
<p>Installing Docker on Linux is a bit of a different process, and depending on the distribution you’re on, it may vary even more. But to be honest, the installation is just as easy (if not easier) as the other two platforms.</p>
<p>The Docker Desktop package on Windows or Mac is a collection of tools like <code>Docker Engine</code>, <code>Docker Compose</code>, <code>Docker Dashboard</code>, <code>Kubernetes</code> and a few other goodies. </p>
<p>On Linux however, you don’t get such a bundle. Instead you install all the necessary tools you need manually. Installation procedures for different distributions are as follows:</p>
<ul>
<li>If you’re on Ubuntu, you may follow the <a target="_blank" href="https://docs.docker.com/engine/install/ubuntu/">Install Docker Engine on Ubuntu</a> section from the official docs.</li>
<li>For other distributions, <em>installation per distro</em> guides are available on the official docs.<ul>
<li><a target="_blank" href="https://docs.docker.com/engine/install/debian/">Install Docker Engine on Debian</a></li>
<li><a target="_blank" href="https://docs.docker.com/engine/install/fedora/">Install Docker Engine on Fedora</a></li>
<li><a target="_blank" href="https://docs.docker.com/engine/install/centos/">Install Docker Engine on CentOS</a></li>
</ul>
</li>
<li>If you’re on a distribution that is not listed in the docs, you may follow the <a target="_blank" href="https://docs.docker.com/engine/install/binaries/">Install Docker Engine from binaries</a> guide instead.</li>
<li>Regardless of the procedure you follow, you’ll have to go through some <a target="_blank" href="https://docs.docker.com/engine/install/linux-postinstall/">Post-installation steps for Linux</a> which are very important.</li>
<li>Once you’re done with the docker installation, you’ll have to install another tool named Docker Compose. You may follow the <a target="_blank" href="https://docs.docker.com/compose/install/">Install Docker Compose</a> guide from the official docs. </li>
</ul>
<p>Once the installation is done, open up the terminal and execute <code>docker --version</code> and <code>docker-compose --version</code> to ensure the success of the installation.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-and-compose-version-on-linux.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Although Docker performs quite well regardless of the platform you’re on, I prefer Linux over the others. Throughout the book, I’ll be switching between my <a target="_blank" href="https://releases.ubuntu.com/20.10/">Ubuntu 20.10</a> and <a target="_blank" href="https://fedoramagazine.org/announcing-fedora-33/">Fedora 33</a> workstations.</p>
<p>Another thing that I would like to clarify right from the get go, is that I won't be using any GUI tool for working with Docker throughout the entire book.</p>
<p>I'm aware of the nice GUI tools available for different platforms, but learning the common docker commands is one of the primary goals of this book.</p>
<h2 id="heading-hello-world-in-docker-intro-to-docker-basics">Hello World in Docker – Intro to Docker Basics</h2>
<p>Now that you have Docker up and running on your machine, it's time for you to run your first container. Open up the terminal and run the following command:</p>
<pre><code>docker run hello-world

# Unable to find image <span class="hljs-string">'hello-world:latest'</span> locally
# latest: Pulling <span class="hljs-keyword">from</span> library/hello-world
# <span class="hljs-number">0e03</span>bdcc26d7: Pull complete 
# Digest: sha256:<span class="hljs-number">4</span>cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc
# Status: Downloaded newer image <span class="hljs-keyword">for</span> hello-world:latest
# 
# Hello <span class="hljs-keyword">from</span> Docker!
# This message shows that your installation appears to be working correctly.
# 
# To generate <span class="hljs-built_in">this</span> message, Docker took the following steps:
#  <span class="hljs-number">1.</span> The Docker client contacted the Docker daemon.
#  <span class="hljs-number">2.</span> The Docker daemon pulled the <span class="hljs-string">"hello-world"</span> image <span class="hljs-keyword">from</span> the Docker Hub.
#     (amd64)
#  <span class="hljs-number">3.</span> The Docker daemon created a <span class="hljs-keyword">new</span> container <span class="hljs-keyword">from</span> that image which runs the
#     executable that produces the output you are currently reading.
#  <span class="hljs-number">4.</span> The Docker daemon streamed that output to the Docker client, which sent it
#     to your terminal.
#
# To <span class="hljs-keyword">try</span> something more ambitious, you can run an Ubuntu container <span class="hljs-keyword">with</span>:
#  $ docker run -it ubuntu bash
# 
# Share images, automate workflows, and more <span class="hljs-keyword">with</span> a free Docker ID:
#  https:<span class="hljs-comment">//hub.docker.com/</span>
#
# For more examples and ideas, <span class="hljs-attr">visit</span>:
#  https:<span class="hljs-comment">//docs.docker.com/get-started/</span>
</code></pre><p>The <a target="_blank" href="https://hub.docker.com/_/hello-world">hello-world</a> image is an example of minimal containerization with Docker. It has a single program compiled from a <a target="_blank" href="https://github.com/docker-library/hello-world/blob/master/hello.c">hello.c</a> file responsible for printing out the message you're seeing on your terminal.</p>
<p>Now in your terminal, you can use the <code>docker ps -a</code> command to have a look at all the containers that are currently running or have run in the past:</p>
<pre><code>docker ps -a

# CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
# <span class="hljs-number">128</span>ec8ceab71        hello-world         <span class="hljs-string">"/hello"</span>            <span class="hljs-number">14</span> seconds ago      Exited (<span class="hljs-number">0</span>) <span class="hljs-number">13</span> seconds ago                      exciting_chebyshev
</code></pre><p>In the output, a container named <code>exciting_chebyshev</code> was run with the container id of <code>128ec8ceab71</code> using the <code>hello-world</code> image. It has <code>Exited (0) 13 seconds ago</code> where the <code>(0)</code> exit code means no error was produced during the runtime of the container.</p>
<p>Now in order to understand what just happened behind the scenes, you'll have to get familiar with the Docker Architecture and three very fundamental concepts of containerization in general, which are as follows:</p>
<ul>
<li>Container</li>
<li>Image</li>
<li>Registry</li>
</ul>
<p>I've listed the three concepts in alphabetical order and will begin my explanations with the first one on the list.</p>
<h3 id="heading-what-is-a-container">What is a Container?</h3>
<p>In the world of containerization, there can not be anything more fundamental than the concept of a container.</p>
<p>The official Docker <a target="_blank" href="https://www.docker.com/resources/what-container">resources</a> site says - </p>
<blockquote>
<p>A container is an abstraction at the application layer that packages code and dependencies together. Instead of virtualizing the entire physical machine, containers virtualize the host operating system only.</p>
</blockquote>
<p>You may consider containers to be the next generation of virtual machines. </p>
<p>Just like virtual machines, containers are completely isolated environments from the host system as well as from each other. They are also a lot lighter than the traditional virtual machine, so a large number of containers can be run simultaneously without affecting the performance of the host system.‌</p>
<p>Containers and virtual machines are actually different ways of virtualizing your physical hardware. The main difference between these two is the method of virtualization.</p>
<p>Virtual machines are usually created and managed by a program known as a hypervisor, like <a target="_blank" href="https://www.virtualbox.org/">Oracle VM VirtualBox</a>, <a target="_blank" href="https://www.vmware.com/">VMware Workstation</a>, <a target="_blank" href="https://www.linux-kvm.org/">KVM</a>, <a target="_blank" href="https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/about/">Microsoft Hyper-V</a> and so on. This hypervisor program usually sits between the host operating system and the virtual machines to act as a medium of communication.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/virtual-machines.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Each virtual machine comes with its own guest operating system which is just as heavy as the host operating system. </p>
<p>The application running inside a virtual machine communicates with the guest operating system, which talks to the hypervisor, which then in turn talks to the host operating system to allocate necessary resources from the physical infrastructure to the running application.</p>
<p>As you can see, there is a long chain of communication between applications running inside virtual machines and the physical infrastructure. The application running inside the virtual machine may take only a small amount of resources, but the guest operating system adds a noticeable overhead.</p>
<p>Unlike a virtual machine, a container does the job of virtualization in a smarter way. Instead of having a complete guest operating system inside a container, it just utilizes the host operating system via the container runtime while maintaining isolation – just like a traditional virtual machine.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/containers.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The container runtime, that is Docker, sits between the containers and the host operating system instead of a hypervisor. The containers then communicate with the container runtime which then communicates with the host operating system to get necessary resources from the physical infrastructure. </p>
<p>As a result of eliminating the entire guest operating system layer, containers are much lighter and less resource-hogging than traditional virtual machines.</p>
<p>As a demonstration of the point, look at the following code block:</p>
<pre><code>uname -a
# Linux alpha-centauri <span class="hljs-number">5.8</span><span class="hljs-number">.0</span><span class="hljs-number">-22</span>-generic #<span class="hljs-number">23</span>-Ubuntu SMP Fri Oct <span class="hljs-number">9</span> <span class="hljs-number">00</span>:<span class="hljs-number">34</span>:<span class="hljs-number">40</span> UTC <span class="hljs-number">2020</span> x86_64 x86_64 x86_64 GNU/Linux

docker run alpine uname -a
# Linux f08dbbe9199b <span class="hljs-number">5.8</span><span class="hljs-number">.0</span><span class="hljs-number">-22</span>-generic #<span class="hljs-number">23</span>-Ubuntu SMP Fri Oct <span class="hljs-number">9</span> <span class="hljs-number">00</span>:<span class="hljs-number">34</span>:<span class="hljs-number">40</span> UTC <span class="hljs-number">2020</span> x86_64 Linux
</code></pre><p>In the code block above, I have executed the <code>uname -a</code> command on my host operating system to print out the kernel details. Then on the next line I've executed the same command inside a container running <a target="_blank" href="https://alpinelinux.org/">Alpine Linux</a>. </p>
<p>As you can see in the output, the container is indeed using the kernel from my host operating system. This goes to prove the point that containers virtualize the host operating system instead of having an operating system of their own.</p>
<p>If you're on a Windows machine, you'll find out that all the containers use the WSL2 kernel. It happens because WSL2 acts as the back-end for Docker on Windows. On macOS the default back-end is a VM running on <a target="_blank" href="https://github.com/moby/hyperkit">HyperKit</a> hypervisor.</p>
<h3 id="heading-what-is-a-docker-image">What is a Docker Image?</h3>
<p>Images are multi-layered self-contained files that act as the template for creating containers. They are like a frozen, read-only copy of a container. Images can be exchanged through registries.</p>
<p>In the past, different container engines had different image formats. But later on, the <a target="_blank" href="https://opencontainers.org/">Open Container Initiative (OCI)</a> defined a standard specification for container images which is complied by the major containerization engines out there. This means that an image built with Docker can be used with another runtime like Podman without any additional hassle.</p>
<p>Containers are just images in running state. When you obtain an image from the internet and run a container using that image, you essentially create another temporary writable layer on top of the previous read-only ones. </p>
<p>This concept will become a lot clearer in upcoming sections of this book. But for now, just keep in mind that images are multi-layered read-only files carrying your application in a desired state inside them.</p>
<h3 id="heading-what-is-a-docker-registry">What is a Docker Registry?</h3>
<p>You've already learned about two very important pieces of the puzzle, <em>Containers</em> and <em>Images</em>. The final piece is the <em>Registry</em>. </p>
<p>An image registry is a centralized place where you can upload your images and can also download images created by others. <a target="_blank" href="https://hub.docker.com/">Docker Hub</a> is the default public registry for Docker. Another very popular image registry is <a target="_blank" href="https://quay.io/">Quay</a> by Red Hat. </p>
<p>Throughout this book I'll be using Docker Hub as my registry of choice.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-hub.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can share any number of public images on Docker Hub for free. People around the world will be able to download them and use them freely. Images that I've uploaded are available on my profile (<a target="_blank" href="https://hub.docker.com/u/fhsinchy">fhsinchy</a>) page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/my-images-on-docker-hub.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Apart from Docker Hub or Quay, you can also create your own image registry for hosting private images. There is also a local registry that runs within your computer that caches images pulled from remote registries.</p>
<h3 id="heading-docker-architecture-overview">Docker Architecture Overview</h3>
<p>Now that you've become familiar with most of the fundamental concepts regarding containerization and Docker, it's time for you to understand how Docker as a software was designed.</p>
<p>The engine consists of three major components:</p>
<ol>
<li><strong>Docker Daemon:</strong> The daemon (<code>dockerd</code>) is a process that keeps running in the background and waits for commands from the client. The daemon is capable of managing various Docker objects.</li>
<li><strong>Docker Client:</strong> The client  (<code>docker</code>) is a command-line interface program mostly responsible for transporting commands issued by users.</li>
<li><strong>REST API:</strong> The REST API acts as a bridge between the daemon and the client. Any command issued using the client passes through the API to finally reach the daemon.</li>
</ol>
<p>According to the official <a target="_blank" href="https://docs.docker.com/get-started/overview/#docker-architecture">docs</a>, </p>
<blockquote>
<p>"Docker uses a client-server architecture. The Docker <em>client</em> talks to the Docker <em>daemon</em>, which does the heavy lifting of building, running, and distributing your Docker containers".</p>
</blockquote>
<p>You as a user will usually execute commands using the client component. The client then use the REST API to reach out to the long running daemon and get your work done.</p>
<h3 id="heading-the-full-picture">The Full Picture</h3>
<p>Okay, enough talking. Now it's time for you to understand how all these pieces of the puzzle you just learned about work in harmony. Before I dive into the explanation of what really happens when you run the <code>docker run hello-world</code> command, let me show you a little diagram I've made:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/docker-run-hello-world.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This image is a slightly modified version of the one found in the official <a target="_blank" href="https://docs.docker.com/engine/images/architecture.svg">docs</a>. The events that occur when you execute the command are as follows:</p>
<ol>
<li>You execute <code>docker run hello-world</code> command where <code>hello-world</code> is the name of an image.</li>
<li>Docker client reaches out to the daemon, tells it to get the <code>hello-world</code> image and run a container from that.</li>
<li>Docker daemon looks for the image within your local repository and realizes that it's not there, resulting in the <code>Unable to find image 'hello-world:latest' locally</code> that's printed on your terminal.</li>
<li>The daemon then reaches out to the default public registry which is Docker Hub and pulls in the latest copy of the <code>hello-world</code> image, indicated by the <code>latest: Pulling from library/hello-world</code> line in your terminal.</li>
<li>Docker daemon then creates a new container from the freshly pulled image.</li>
<li>Finally Docker daemon runs the container created using the <code>hello-world</code> image outputting the wall of text on your terminal.</li>
</ol>
<p>It's the default behavior of Docker daemon to look for images in the hub that are not present locally. But once an image has been fetched, it'll stay in the local cache. So if you execute the command again, you won't see the following lines in the output:</p>
<pre><code>Unable to find image <span class="hljs-string">'hello-world:latest'</span> locally
<span class="hljs-attr">latest</span>: Pulling <span class="hljs-keyword">from</span> library/hello-world
<span class="hljs-number">0e03</span>bdcc26d7: Pull complete
<span class="hljs-attr">Digest</span>: sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9
<span class="hljs-attr">Status</span>: Downloaded newer image <span class="hljs-keyword">for</span> hello-world:latest
</code></pre><p>If there is a newer version of the image available on the public registry, the daemon will fetch the image again. That <code>:latest</code> is a tag. Images usually have meaningful tags to indicate versions or builds. You'll learn about this in greater detail later on.</p>
<h2 id="heading-docker-container-manipulation-basics">Docker Container Manipulation Basics</h2>
<p>In the previous sections, you've learned about the building blocks of Docker and have also run a container using the <code>docker run</code> command. </p>
<p>In this section, you'll be learning about container manipulation in a lot more detail. Container manipulation is one of the most common task you'll be performing every single day, so having a proper understanding of the various commands is crucial.</p>
<p>Keep in mind, though, that this is not an exhaustive list of all the commands you can execute on Docker. I'll be talking only about the most common ones. Anytime you want to learn more about the available commands, just visit the official <a target="_blank" href="https://docs.docker.com/engine/reference/commandline/container/">reference</a> for the Docker command-line.</p>
<h3 id="heading-how-to-run-a-container">How to Run a Container</h3>
<p>Previously you've used <code>docker run</code> to create and start a container using the <code>hello-world</code> image. The generic syntax for this command is as follows:</p>
<pre><code>docker run &lt;image name&gt;
</code></pre><p>Although this is a perfectly valid command, there is a better way of dispatching commands to the <code>docker</code> daemon. </p>
<p>Prior to version <code>1.13</code>, Docker had only the previously mentioned command syntax. Later on, the command-line was <a target="_blank" href="https://www.docker.com/blog/whats-new-in-docker-1-13/">restructured</a> to have the following syntax:</p>
<pre><code>docker &lt;object&gt; &lt;command&gt; &lt;options&gt;
</code></pre><p>In this syntax:</p>
<ul>
<li><code>object</code> indicates the type of Docker object you'll be manipulating. This can be a <code>container</code>, <code>image</code>, <code>network</code> or <code>volume</code> object.</li>
<li><code>command</code> indicates the task to be carried out by the daemon, that is the <code>run</code> command.</li>
<li><code>options</code> can be any valid parameter that can override the default behavior of the command, like the <code>--publish</code> option for port mapping.</li>
</ul>
<p>Now, following this syntax, the <code>run</code> command can be written as follows:</p>
<pre><code>docker container run &lt;image name&gt;
</code></pre><p>The <code>image name</code> can be of any image from an online registry or your local system. As an example, you can try to run a container using the <a target="_blank" href="https://hub.docker.com/r/fhsinchy/hello-dock">fhsinchy/hello-dock</a> image. This image contains a simple <a target="_blank" href="https://vuejs.org/">Vue.js</a> application that runs on port 80 inside the container. </p>
<p>To run a container using this image, execute following command on your terminal:</p>
<pre><code>docker container run --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> fhsinchy/hello-dock

# /docker-entrypoint.sh: <span class="hljs-regexp">/docker-entrypoint.d/</span> is not empty, will attempt to perform configuration
# /docker-entrypoint.sh: Looking <span class="hljs-keyword">for</span> shell scripts <span class="hljs-keyword">in</span> /docker-entrypoint.d/
# /docker-entrypoint.sh: Launching /docker-entrypoint.d/<span class="hljs-number">10</span>-listen-on-ipv6-by-<span class="hljs-keyword">default</span>.sh
# <span class="hljs-number">10</span>-listen-on-ipv6-by-<span class="hljs-keyword">default</span>.sh: Getting the checksum <span class="hljs-keyword">of</span> /etc/nginx/conf.d/<span class="hljs-keyword">default</span>.conf
# <span class="hljs-number">10</span>-listen-on-ipv6-by-<span class="hljs-keyword">default</span>.sh: Enabled listen on IPv6 <span class="hljs-keyword">in</span> /etc/nginx/conf.d/<span class="hljs-keyword">default</span>.conf
# /docker-entrypoint.sh: Launching /docker-entrypoint.d/<span class="hljs-number">20</span>-envsubst-on-templates.sh
# /docker-entrypoint.sh: Configuration complete; ready <span class="hljs-keyword">for</span> start up
</code></pre><p>The command is pretty self-explanatory. The only portion that may require some explanation is the <code>--publish 8080:80</code> portion which will be explained in the next sub-section.</p>
<h3 id="heading-how-to-publish-a-port">How to Publish a Port</h3>
<p>Containers are isolated environments. Your host system doesn't know anything about what's going on inside a container. Hence, applications running inside a container remain inaccessible from the outside.</p>
<p>To allow access from outside of a container, you must publish the appropriate port inside the container to a port on your local network. The common syntax for the <code>--publish</code> or <code>-p</code> option is as follows:</p>
<pre><code>--publish &lt;host port&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">container</span> <span class="hljs-attr">port</span>&gt;</span></span>
</code></pre><p>When you wrote <code>--publish 8080:80</code> in the previous sub-section, it meant any request sent to port 8080 of your host system will be forwarded to port 80 inside the container‌.</p>
<p>Now to access the application on your browser, visit <code>http://127.0.0.1:8080</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can stop the container by simply hitting the <code>ctrl + c</code> key combination while the terminal window is in focus or closing off the terminal window completely.</p>
<h3 id="heading-how-to-use-detached-mode">How to Use Detached Mode</h3>
<p>Another very popular option of the <code>run</code> command is the <code>--detach</code> or <code>-d</code> option. In the example above, in order for the container to keep running, you had to keep the terminal window open. Closing the terminal window also stopped the running container.</p>
<p>This is because, by default, containers run in the foreground and attach themselves to the terminal like any other normal program invoked from the terminal. </p>
<p>In order to override this behavior and keep a container running in background, you can include the <code>--detach</code> option with the <code>run</code> command as follows:</p>
<pre><code>docker container run --detach --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> fhsinchy/hello-dock

# <span class="hljs-number">9</span>f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc
</code></pre><p>Unlike the previous example, you won't get a wall of text thrown at you this time. Instead what you'll get is the ID of the newly created container.</p>
<p>The order of the options you provide doesn't really matter. If you put the <code>--publish</code> option before the <code>--detach</code> option, it'll work just the same. One thing that you have to keep in mind in case of the <code>run</code> command is that the image name must come last. If you put anything after the image name then that'll be passed as an argument to the container entry-point (explained in the <a class="post-section-overview" href="#executing-commands-inside-a-container">Executing Commands Inside a Container</a> sub-section) and may result in unexpected situations.</p>
<h3 id="heading-how-to-list-containers">How to List Containers</h3>
<p>The <code>container ls</code> command can be used to list out containers that are currently running. To do so execute following command:</p>
<pre><code>docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">5</span> seconds ago       Up <span class="hljs-number">5</span> seconds        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   gifted_sammet
</code></pre><p>A container named <code>gifted_sammet</code> is running. It was created <code>5 seconds ago</code> and the status is <code>Up 5 seconds,</code> which indicates that the container has been running fine since its creation.</p>
<p>The <code>CONTAINER ID</code> is <code>9f21cb777058</code> which is the first 12 characters of the full container ID. The full container ID is <code>9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc</code> which is 64 characters long. This full container ID was printed as the output of the <code>docker container run</code> command in the previous section.</p>
<p>Listed under the <code>PORTS</code> column, port 8080 from your local network is pointing towards port 80 inside the container. The name <code>gifted_sammet</code> is generated by Docker and can be something completely different in your computer.</p>
<p>The <code>container ls</code> command only lists the containers that are currently running on your system. In order to list out the containers that have run in the past you can use the <code>--all</code> or <code>-a</code> option.</p>
<pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                     PORTS                  NAMES
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">2</span> minutes ago       Up <span class="hljs-number">2</span> minutes               <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   gifted_sammet
# <span class="hljs-number">6</span>cf52771dde1        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">3</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">3</span> minutes ago                          reverent_torvalds
# <span class="hljs-number">128</span>ec8ceab71        hello-world           <span class="hljs-string">"/hello"</span>                 <span class="hljs-number">4</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">4</span> minutes ago                          exciting_chebyshev
</code></pre><p>As you can see, the second container in the list <code>reverent_torvalds</code> was created earlier and has exited with the status code 0, which indicates that no error was produced during the runtime of the container.</p>
<h3 id="heading-how-to-name-or-rename-a-container">How to Name or Rename a Container</h3>
<p>By default, every container has two identifiers. They are as follows:</p>
<ul>
<li><code>CONTAINER ID</code> - a random 64 character-long string.</li>
<li><code>NAME</code> - combination of two random words, joined with an underscore.</li>
</ul>
<p>Referring to a container based on these two random identifiers is kind of inconvenient. It would be great if the containers could be referred to using a name defined by you.</p>
<p>Naming a container can be achieved using the <code>--name</code> option. To run another container using the <code>fhsinchy/hello-dock</code> image with the name <code>hello-dock-container</code> you can execute the following command:</p>
<pre><code>docker container run --detach --publish <span class="hljs-number">8888</span>:<span class="hljs-number">80</span> --name hello-dock-container fhsinchy/hello-dock

# b1db06e400c4c5e81a93a64d30acc1bf821bed63af36cab5cdb95d25e114f5fb
</code></pre><p>The 8080 port on local network is occupied by the <code>gifted_sammet</code> container (the container created in the previous sub-section). That's why you'll have to use a different port number, like 8888. Now to verify, run the <code>container ls</code> command:</p>
<pre><code>docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">28</span> seconds ago      Up <span class="hljs-number">26</span> seconds       <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8888</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">4</span> minutes ago       Up <span class="hljs-number">4</span> minutes        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   gifted_sammet
</code></pre><p>A new container with the name of <code>hello-dock-container</code> has been started.</p>
<p>You can even rename old containers using the <code>container rename</code> command. Syntax for the command is as follows:</p>
<pre><code>docker container rename &lt;container identifier&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">new</span> <span class="hljs-attr">name</span>&gt;</span></span>
</code></pre><p>To rename the <code>gifted_sammet</code> container to <code>hello-dock-container-2</code>, execute following command:</p>
<pre><code>docker container rename gifted_sammet hello-dock-container<span class="hljs-number">-2</span>
</code></pre><p>The command doesn't yield any output but you can verify that the changes have taken place using the <code>container ls</code> command. The <code>rename</code> command works for containers both in running state and stopped state.</p>
<h3 id="heading-how-to-stop-or-kill-a-running-container">How to Stop or Kill a Running Container</h3>
<p>Containers running in the foreground can be stopped by simply closing the terminal window or hitting <code>ctrl + c</code>. Containers running in the background, however, can not be stopped in the same way.</p>
<p>There are two commands that deal with this task. The first one is the <code>container stop</code> command. Generic syntax for the command is as follows:</p>
<pre><code>docker container stop &lt;container identifier&gt;
</code></pre><p>Where <code>container identifier</code> can either be the id or the name of the container. </p>
<p>I hope that you remember the container you started in the previous section. It's still running in the background. Get the identifier for that container using <code>docker container ls</code> (I'll be using <code>hello-dock-container</code> container for this demo). Now execute the following command to stop the container:</p>
<pre><code>docker container stop hello-dock-container

# hello-dock-container
</code></pre><p>If you use the name as identifier, you'll get the name thrown back to you as output. The <code>stop</code> command shuts down a container gracefully by sending a <code>SIGTERM</code> signal. If the container doesn't stop within a certain period, a <code>SIGKILL</code> signal is sent which shuts down the container immediately.</p>
<p>In cases where you want to send a <code>SIGKILL</code> signal instead of a <code>SIGTERM</code> signal, you may use the <code>container kill</code> command instead. The <code>container kill</code> command follows the same syntax as the <code>stop</code> command.</p>
<pre><code>docker container kill hello-dock-container<span class="hljs-number">-2</span>

# hello-dock-container<span class="hljs-number">-2</span>
</code></pre><h3 id="heading-how-to-restart-a-container">How to Restart a Container</h3>
<p>When I say restart I mean two scenarios specifically. They are as follows:</p>
<ul>
<li>Restarting a container that has been previously stopped or killed.</li>
<li>Rebooting a running container.</li>
</ul>
<p>As you've already learned from a previous sub-section, stopped containers remain in your system. If you want you can restart them. The <code>container start</code> command can be used to start any stopped or killed container. The syntax of the command is as follows:</p>
<pre><code>docker container start &lt;container identifier&gt;
</code></pre><p>You can get the list of all containers by executing the <code>container ls --all</code> command. Then look for the containers with <code>Exited</code> status.</p>
<pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                        PORTS               NAMES
# b1db06e400c4        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">3</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">47</span> seconds ago                         hello-dock-container
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">7</span> minutes ago       Exited (<span class="hljs-number">137</span>) <span class="hljs-number">17</span> seconds ago                       hello-dock-container<span class="hljs-number">-2</span>
# <span class="hljs-number">6</span>cf52771dde1        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">7</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">7</span> minutes ago                          reverent_torvalds
# <span class="hljs-number">128</span>ec8ceab71        hello-world           <span class="hljs-string">"/hello"</span>                 <span class="hljs-number">9</span> minutes ago       Exited (<span class="hljs-number">0</span>) <span class="hljs-number">9</span> minutes ago                          exciting_chebyshev
</code></pre><p>Now to restart the <code>hello-dock-container</code> container, you may execute the following command:</p>
<pre><code>docker container start hello-dock-container

# hello-dock-container
</code></pre><p>Now you can ensure that the container is running by looking at the list of running containers using the <code>container ls</code> command. </p>
<p>The <code>container start</code> command starts any container in detached mode by default and retains any port configurations made previously. So if you visit <code>http://127.0.0.1:8080</code> now, you should be able to access the <code>hello-dock</code> application just like before.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, in scenarios where you would like to reboot a running container you may use the <code>container restart</code> command. The <code>container restart</code> command follows the exact syntax as the <code>container start</code> command.</p>
<pre><code>docker container restart hello-dock-container<span class="hljs-number">-2</span>

# hello-dock-container<span class="hljs-number">-2</span>
</code></pre><p>The main difference between the two commands is that the <code>container restart</code> command attempts to stop the target container and then starts it back up again, whereas the start command just starts an already stopped container.</p>
<p>In case of a stopped container, both commands are exactly the same. But in case of a running container, you must use the <code>container restart</code> command.</p>
<h3 id="heading-how-to-create-a-container-without-running">How to Create a Container Without Running</h3>
<p>So far in this section, you've started containers using the <code>container run</code> command which is in reality a combination of two separate commands. These commands are as follows:</p>
<ul>
<li><code>container create</code> command creates a container from a given image.</li>
<li><code>container start</code> command starts a container that has been already created.</li>
</ul>
<p>Now, to perform the demonstration shown in the "How to Run a Container" section using these two commands, you can do something like the following:</p>
<pre><code>docker container create --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> fhsinchy/hello-dock

# <span class="hljs-number">2e7</span>ef5098bab92f4536eb9a372d9b99ed852a9a816c341127399f51a6d053856

docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
# <span class="hljs-number">2e7</span>ef5098bab        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">30</span> seconds ago      Created                                 hello-dock
</code></pre><p>Evident by the output of the <code>container ls --all</code> command, a container with the name of <code>hello-dock</code> has been created using the <code>fhsinchy/hello-dock</code> image. The <code>STATUS</code> of the container is <code>Created</code> at the moment, and, given that it's not running, it won't be listed without the use of the <code>--all</code> option. </p>
<p>Once the container has been created, it can be started using the <code>container start</code> command.</p>
<pre><code>docker container start hello-dock

# hello-dock

docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED              STATUS              PORTS                  NAMES
# <span class="hljs-number">2e7</span>ef5098bab        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   About a minute ago   Up <span class="hljs-number">29</span> seconds       <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock
</code></pre><p>The container <code>STATUS</code> has changed from <code>Created</code> to <code>Up 29 seconds</code> which indicates that the container is now in running state. The port configuration has also shown up in the <code>PORTS</code> column which was previously empty.‌</p>
<p>Although you can get away with the <code>container run</code> command for the majority of the scenarios, there will be some situations later on in the book that require you to use this <code>container create</code> command.</p>
<h3 id="heading-how-to-remove-dangling-containers">How to Remove Dangling Containers</h3>
<p>As you've already seen, containers that have been stopped or killed remain in the system. These dangling containers can take up space or can conflict with newer containers.</p>
<p>In order to remove a stopped container you can use the <code>container rm</code> command. The generic syntax is as follows:</p>
<pre><code>docker container rm &lt;container identifier&gt;
</code></pre><p>To find out which containers are not running, use the <code>container ls --all</code> command and look for containers with <code>Exited</code> status.</p>
<pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                      PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">6</span> minutes ago       Up About a minute           <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8888</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">10</span> minutes ago      Up About a minute           <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container<span class="hljs-number">-2</span>
# <span class="hljs-number">6</span>cf52771dde1        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">10</span> minutes ago      Exited (<span class="hljs-number">0</span>) <span class="hljs-number">10</span> minutes ago                          reverent_torvalds
# <span class="hljs-number">128</span>ec8ceab71        hello-world           <span class="hljs-string">"/hello"</span>                 <span class="hljs-number">12</span> minutes ago      Exited (<span class="hljs-number">0</span>) <span class="hljs-number">12</span> minutes ago                          exciting_chebyshev
</code></pre><p>As can be seen in the output, the containers with ID <code>6cf52771dde1</code> and <code>128ec8ceab71</code> are not running. To remove the <code>6cf52771dde1</code> you can execute the following command:</p>
<pre><code>docker container rm <span class="hljs-number">6</span>cf52771dde1

# <span class="hljs-number">6</span>cf52771dde1
</code></pre><p>You can check if the container was deleted or not by using the <code>container ls</code> command. You can also remove multiple containers at once by passing their identifiers one after another separated by spaces.</p>
<p>Or, instead of removing individual containers, if you want to remove all dangling containers at one go, you can use the <code>container prune</code> command.</p>
<p>You can check the container list using the <code>container ls --all</code> command to make sure that the dangling containers have been removed:</p>
<pre><code>docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">8</span> minutes ago       Up <span class="hljs-number">3</span> minutes        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8888</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container
# <span class="hljs-number">9</span>f21cb777058        fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">12</span> minutes ago      Up <span class="hljs-number">3</span> minutes        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-container<span class="hljs-number">-2</span>
</code></pre><p>If you are following the book exactly as written so far, you should only see the <code>hello-dock-container</code> and <code>hello-dock-container-2</code> in the list. I would suggest stopping and removing both containers before going on to the next section.</p>
<p>There is also the <code>--rm</code> option for the <code>container run</code>  and <code>container start</code> commands which indicates that you want the containers removed as soon as they're stopped. To start another <code>hello-dock</code> container with the <code>--rm</code> option, execute the following command:</p>
<pre><code>docker container run --rm --detach --publish <span class="hljs-number">8888</span>:<span class="hljs-number">80</span> --name hello-dock-volatile fhsinchy/hello-dock

# <span class="hljs-number">0</span>d74e14091dc6262732bee226d95702c21894678efb4043663f7911c53fb79f3
</code></pre><p>You can use the <code>container ls</code> command to verify that the container is running:</p>
<pre><code>docker container ls

# CONTAINER ID   IMAGE                 COMMAND                  CREATED              STATUS              PORTS                  NAMES
# <span class="hljs-number">0</span>d74e14091dc   fhsinchy/hello-dock   <span class="hljs-string">"/docker-entrypoint.…"</span>   About a minute ago   Up About a minute   <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8888</span>-&gt;<span class="hljs-number">80</span>/tcp   hello-dock-volatile
</code></pre><p>Now if you stop the container and then check again with the <code>container ls --all</code> command:</p>
<pre><code>docker container stop hello-dock-volatile

# hello-dock-volatile

docker container ls --all

# CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
</code></pre><p>The container has been removed automatically. From now on I'll use the <code>--rm</code> option for most of the containers. I'll explicitly mention where it's not needed.</p>
<h3 id="heading-how-to-run-a-container-in-interactive-mode">How to Run a Container in Interactive Mode</h3>
<p>So far you've only run containers created from either the <a target="_blank" href="https://hub.docker.com/_/hello-world">hello-world</a> image or the <a target="_blank" href="https://hub.docker.com/r/fhsinchy/hello-dock">fhsinchy/hello-dock</a> image. These images are made for executing simple programs that are not interactive.</p>
<p>Well, all images are not that simple. Images can encapsulate an entire Linux distribution inside them. </p>
<p>Popular distributions such as <a target="_blank" href="https://ubuntu.com/">Ubuntu</a>, <a target="_blank" href="https://fedora.org/">Fedora</a>, and <a target="_blank" href="https://debian.org/">Debian</a> all have official Docker images available in the hub. Programming languages such as <a target="_blank" href="https://hub.docker.com/_/python">python</a>, <a target="_blank" href="https://hub.docker.com/_/php">php</a>, <a target="_blank" href="https://hub.docker.com/_/golang">go</a> or run-times like <a target="_blank" href="https://hub.docker.com/_/node">node</a> and <a target="_blank" href="https://hub.docker.com/r/hayd/deno">deno</a> all have their official images.</p>
<p>These images do not just run some pre-configured program. These are instead configured to run a shell by default. In case of the operating system images it can be something like <code>sh</code> or <code>bash</code> and in case of the programming languages or run-times, it is usually their default language shell.</p>
<p>As you may have already learned from your previous experiences with computers, shells are interactive programs. An image configured to run such a program is an interactive image. These images require a special <code>-it</code> option to be passed in the <code>container run</code> command.</p>
<p>As an example, if you run a container using the <code>ubuntu</code> image by executing <code>docker container run ubuntu</code> you'll see nothing happens. But if you execute the same command with the <code>-it</code> option, you should land directly on bash inside the Ubuntu container.</p>
<pre><code>docker container run --rm -it ubuntu

# root@dbb1f56b9563:<span class="hljs-regexp">/# cat /</span>etc/os-release
# NAME=<span class="hljs-string">"Ubuntu"</span>
# VERSION=<span class="hljs-string">"20.04.1 LTS (Focal Fossa)"</span>
# ID=ubuntu
# ID_LIKE=debian
# PRETTY_NAME=<span class="hljs-string">"Ubuntu 20.04.1 LTS"</span>
# VERSION_ID=<span class="hljs-string">"20.04"</span>
# HOME_URL=<span class="hljs-string">"https://www.ubuntu.com/"</span>
# SUPPORT_URL=<span class="hljs-string">"https://help.ubuntu.com/"</span>
# BUG_REPORT_URL=<span class="hljs-string">"https://bugs.launchpad.net/ubuntu/"</span>
# PRIVACY_POLICY_URL=<span class="hljs-string">"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"</span>
# VERSION_CODENAME=focal
# UBUNTU_CODENAME=focal
</code></pre><p>As you can see from the output of the <code>cat /etc/os-release</code> command, I am indeed interacting with the bash running inside the Ubuntu container.</p>
<p>The <code>-it</code> option sets the stage for you to interact with any interactive program inside a container. This option is actually two separate options mashed together.</p>
<ul>
<li>The <code>-i</code> or <code>--interactive</code> option connects you to the input stream of the container, so that you can send inputs to bash.</li>
<li>The <code>-t</code> or <code>--tty</code> option makes sure that you get some good formatting and a native terminal-like experience by allocating a pseudo-tty.</li>
</ul>
<p>You need to use the <code>-it</code> option whenever you want to run a container in interactive mode. Another example can be running the <code>node</code> image as follows:</p>
<pre><code>docker container run -it node

# Welcome to Node.js v15<span class="hljs-number">.0</span><span class="hljs-number">.0</span>.
# Type <span class="hljs-string">".help"</span> <span class="hljs-keyword">for</span> more information.
# &gt; [<span class="hljs-string">'farhan'</span>, <span class="hljs-string">'hasin'</span>, <span class="hljs-string">'chowdhury'</span>].map(<span class="hljs-function"><span class="hljs-params">name</span> =&gt;</span> name.toUpperCase())
# [ <span class="hljs-string">'FARHAN'</span>, <span class="hljs-string">'HASIN'</span>, <span class="hljs-string">'CHOWDHURY'</span> ]
</code></pre><p>Any valid JavaScript code can be executed in the node shell. Instead of writing <code>-it</code> you can be more verbose by writing <code>--interactive --tty</code> separately.</p>
<h3 id="heading-how-to-execute-commands-inside-a-container">How to Execute Commands Inside a Container</h3>
<p>In the <a target="_blank" href="https://www.freecodecamp.org/news/@fhsinchy/s/the-docker-handbook/~/drafts/-MS1b3opwENd_9qH1jTO/hello-world-in-docker">Hello World in Docker</a> section of this book, you've seen me executing a command inside an Alpine Linux container. It went something like this:</p>
<pre><code>docker run alpine uname -a
# Linux f08dbbe9199b <span class="hljs-number">5.8</span><span class="hljs-number">.0</span><span class="hljs-number">-22</span>-generic #<span class="hljs-number">23</span>-Ubuntu SMP Fri Oct <span class="hljs-number">9</span> <span class="hljs-number">00</span>:<span class="hljs-number">34</span>:<span class="hljs-number">40</span> UTC <span class="hljs-number">2020</span> x86_64 Linux
</code></pre><p>In this command, I've executed the <code>uname -a</code> command inside an Alpine Linux container. Scenarios like this (where all you want to do is to execute a certain command inside a certain container) are pretty common.</p>
<p>Assume that you want encode a string using the <code>base64</code> program. This is something that's available in almost any Linux or Unix based operating system (but not on Windows). </p>
<p>In this situation you can quickly spin up a container using images like <a target="_blank" href="https://hub.docker.com/_/busybox">busybox</a> and let it do the job.</p>
<p>The generic syntax for encoding a string using <code>base64</code> is as follows:</p>
<pre><code>echo -n my-secret | base64

# bXktc2VjcmV0
</code></pre><p>And the generic syntax for passing a command to a container that is not running is as follows:</p>
<pre><code>docker container run &lt;image name&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">command</span>&gt;</span></span>
</code></pre><p>To perform the base64 encoding using the busybox image, you can execute the following command:</p>
<pre><code>docker container run --rm busybox sh -c <span class="hljs-string">"echo -n my-secret | base64

# bXktc2VjcmV0</span>
</code></pre><p>What happens here is that, in a <code>container run</code> command, whatever you pass after the image name gets passed to the default entry point of the image. </p>
<p>An entry point is like a gateway to the image. Most of the images except the executable images (explained in the <a target="_blank" href="https://www.freecodecamp.org/news/@fhsinchy/s/the-docker-handbook/~/drafts/-MS1b3opwENd_9qH1jTO/container-manipulation-basics#working-with-executable-images">Working With Executable Images</a> sub-section) use shell or <code>sh</code> as the default entry-point. So any valid shell command can be passed to them as arguments.</p>
<h3 id="heading-how-to-work-with-executable-images">How to Work With Executable Images</h3>
<p>In the previous section, I briefly mentioned executable images. These images are designed to behave like executable programs.</p>
<p>Take for example my <a target="_blank" href="https://github.com/fhsinchy/rmbyext">rmbyext</a> project. This is a simple Python script capable of recursively deleting files of given extensions. To learn more about the project, you can checkout the repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/fhsinchy/rmbyext">https://github.com/fhsinchy/rmbyext</a></div>
<p>If you have both Git and Python installed, you can install this script by executing the following command:</p>
<pre><code>pip install git+https:<span class="hljs-comment">//github.com/fhsinchy/rmbyext.git#egg=rmbyext</span>
</code></pre><p>Assuming Python has been set up properly on your system, the script should be available anywhere through the terminal. The generic syntax for using this script is as follows:</p>
<pre><code>rmbyext &lt;file extension&gt;
</code></pre><p>To test it out, open up your terminal inside an empty directory and create some files in it with different extensions. You can use the <code>touch</code> command to do so. Now, I have a directory on my computer with the following files:</p>
<pre><code>touch a.pdf b.pdf c.txt d.pdf e.txt

ls

# a.pdf  b.pdf  c.txt  d.pdf  e.txt
</code></pre><p>To delete all the <code>pdf</code> files from this directory, you can execute the following command:</p>
<pre><code>rmbyext pdf

# Removing: PDF
# b.pdf
# a.pdf
# d.pdf
</code></pre><p>An executable image for this program should be able to take extensions of files as arguments and delete them just like the <code>rmbyext</code> program did.</p>
<p>The <a target="_blank" href="https://hub.docker.com/r/fhsinchy/rmbyext">fhsinchy/rmbyext</a> image behaves in a similar manner. This image contains a copy of the <code>rmbyext</code> script and is configured to run the script on a directory <code>/zone</code> inside the container.</p>
<p>Now the problem is that containers are isolated from your local system, so the <code>rmbyext</code> program running inside the container doesn't have any access to your local file system. So, if somehow you can map the local directory containing the <code>pdf</code> files to the <code>/zone</code> directory inside the container, the files should be accessible to the container.</p>
<p>One way to grant a container direct access to your local file system is by using <a target="_blank" href="https://docs.docker.com/storage/bind-mounts/">bind mounts</a>. </p>
<p>A bind mount lets you form a two way data binding between the content of a local file system directory (source) and another directory inside a container (destination). This way any changes made in the destination directory will take effect on the source directory and vise versa.</p>
<p>Let's see a bind mount in action. To delete files using this image instead of the program itself, you can execute the following command:</p>
<pre><code>docker container run --rm -v $(pwd):<span class="hljs-regexp">/zone fhsinchy/</span>rmbyext pdf

# Removing: PDF
# b.pdf
# a.pdf
# d.pdf
</code></pre><p>As you may have already guessed by seeing the <code>-v $(pwd):/zone</code> part in the command, the  <code>-v</code> or <code>--volume</code> option is used for creating a bind mount for a container. This option can take three fields separated by colons (<code>:</code>). The generic syntax for the option is as follows:</p>
<pre><code>--volume &lt;local file system directory absolute path&gt;:&lt;container file system directory absolute path&gt;:&lt;read write access&gt;
</code></pre><p>The third field is optional but you must pass the absolute path of your local directory and the absolute path of the directory inside the container. </p>
<p>The source directory in my case is <code>/home/fhsinchy/the-zone</code>. Given that my terminal is opened inside the directory, <code>$(pwd)</code> will be replaced with <code>/home/fhsinchy/the-zone</code> which contains the previously mentioned <code>.pdf</code> and <code>.txt</code> files. </p>
<p>You can learn more about <a target="_blank" href="https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html">command substitution here</a> if you want to.</p>
<p>The <code>--volume</code> or <code>-v</code> option is valid for the <code>container run</code> as well as the <code>container create</code> commands. We'll explore volumes in greater detail in the upcoming sections so don't worry if you didn't understand them very well here.</p>
<p>The difference between a regular image and an executable one is that the entry-point for an executable image is set to a custom program instead of <code>sh</code>, in this case the <code>rmbyext</code> program. And as you've learned in the previous sub-section, anything you write after the image name in a <code>container run</code> command gets passed to the entry-point of the image.</p>
<p>So in the end the <code>docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf</code> command translates to <code>rmbyext pdf</code> inside the container. Executable images are not that common in the wild but can be very useful in certain cases.</p>
<h2 id="heading-docker-image-manipulation-basics">Docker Image Manipulation Basics</h2>
<p>Now that you have a solid understanding of how to run containers using publicly available images, it's time for you to learn about creating your very own images.</p>
<p>In this section, you'll learn the fundamentals of creating images, running containers using them, and sharing them online.</p>
<p>I would suggest you to install <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> with the official <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker">Docker Extension</a> from the marketplace. This will greatly help your development experience.</p>
<h3 id="heading-how-to-create-a-docker-image">How to Create a Docker Image</h3>
<p>As I've already explained in the <a class="post-section-overview" href="#image">Hello World in Docker</a> section, images are multi-layered self-contained files that act as the template for creating Docker containers. They are like a frozen, read-only copy of a container.</p>
<p>In order to create an image using one of your programs you must have a clear vision of what you want from the image. Take the official <a target="_blank" href="https://hub.docker.com/_/nginx">nginx</a> image, for example. You can start a container using this image simply by executing the following command:</p>
<pre><code>docker container run --rm --detach --name <span class="hljs-keyword">default</span>-nginx --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> nginx

# b379ecd5b6b9ae27c144e4fa12bdc5d0635543666f75c14039eea8d5f38e3f56

docker container ls

# CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b379ecd5b6b9        nginx               <span class="hljs-string">"/docker-entrypoint.…"</span>   <span class="hljs-number">8</span> seconds ago       Up <span class="hljs-number">8</span> seconds        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   <span class="hljs-keyword">default</span>-nginx
</code></pre><p>Now, if you visit <code>http://127.0.0.1:8080</code> in the browser, you'll see a default response page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>That's all nice and good, but what if you want to make a custom NGINX image which functions exactly like the official one, but that's built by you? That's a completely valid scenario to be honest. In fact, let's do that.‌</p>
<p>In order to make a custom NGINX image, you must have a clear picture of what the final state of the image will be. In my opinion the image should be as follows:</p>
<ul>
<li>The image should have NGINX pre-installed which can be done using a package manager or can be built from source.</li>
<li>The image should start NGINX automatically upon running.</li>
</ul>
<p>That's simple. If you've cloned the project repository linked in this book, go inside the project root and look for a directory named <code>custom-nginx</code> in there. </p>
<p>Now, create a new file named <code>Dockerfile</code> inside that directory. A <code>Dockerfile</code> is a collection of instructions that, once processed by the daemon, results in an image. Content for the <code>Dockerfile</code> is as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install nginx -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>Images are multi-layered files and in this file, each line (known as instructions) that you've written creates a layer for your image.</p>
<ul>
<li>Every valid <code>Dockerfile</code> starts with a <code>FROM</code> instruction. This instruction sets the base image for your resultant image. By setting <code>ubuntu:latest</code> as the base image here, you get all the goodness of Ubuntu already available in your custom image, so you can use things like the <code>apt-get</code> command for easy package installation.</li>
<li>The <code>EXPOSE</code> instruction is used to indicate the port that needs to be published. Using this instruction doesn't mean that you won't need to <code>--publish</code> the port. You'll still need to use the <code>--publish</code> option explicitly. This <code>EXPOSE</code> instruction works like a documentation for someone who's trying to run a container using your image. It also has some other uses that I won't be discussing here.</li>
<li>The <code>RUN</code> instruction in a <code>Dockerfile</code> executes a command inside the container shell. The <code>apt-get update &amp;&amp; apt-get install nginx -y</code> command checks for updated package versions and installs NGINX. The <code>apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</code> command is used for clearing the package cache because you don't want any unnecessary baggage in your image. These two commands are simple Ubuntu stuff, nothing fancy. The <code>RUN</code> instructions here are written in <code>shell</code> form. These can also be written in <code>exec</code> form. You can consult the <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#run">official reference</a> for more information.</li>
<li>Finally the <code>CMD</code> instruction sets the default command for your image. This instruction is written in <code>exec</code> form here comprising of three separate parts. Here, <code>nginx</code> refers to the NGINX executable. The <code>-g</code> and <code>daemon off</code> are options for NGINX. Running NGINX as a single process inside containers is considered a best practice hence the usage of this option. The <code>CMD</code> instruction can also be written in <code>shell</code> form. You can consult the <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#cmd">official reference</a> for more information.</li>
</ul>
<p>Now that you have a valid <code>Dockerfile</code> you can build an image out of it. Just like the container related commands, the image related commands can be issued using the following syntax:</p>
<pre><code>docker image &lt;command&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">options</span>&gt;</span></span>
</code></pre><p>To build an image using the <code>Dockerfile</code> you just wrote, open up your terminal inside the <code>custom-nginx</code> directory and execute the following command:</p>
<pre><code>docker image build .

# Sending build context to Docker daemon  <span class="hljs-number">3.584</span>kB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">4</span> : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step <span class="hljs-number">2</span>/<span class="hljs-number">4</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">9</span>eae86582ec7
# Removing intermediate container <span class="hljs-number">9</span>eae86582ec7
#  ---&gt; <span class="hljs-number">8235</span>bd799a56
# Step <span class="hljs-number">3</span>/<span class="hljs-number">4</span> : RUN apt-get update &amp;&amp;     apt-get install nginx -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; Running in a44725cbb3fa
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container a44725cbb3fa
#  ---&gt; 3066bd20292d
# Step 4/4 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 4792e4691660
# Removing intermediate container 4792e4691660
#  ---&gt; 3199372aa3fc
# Successfully built 3199372aa3fc</span>
</code></pre><p>To perform an image build, the daemon needs two very specific pieces of information. These are the name of the <code>Dockerfile</code> and the build context. In the command issued above:</p>
<ul>
<li><code>docker image build</code> is the command for building the image. The daemon finds any file named <code>Dockerfile</code> within the context.</li>
<li>The <code>.</code> at the end sets the context for this build. The context means the directory accessible by the daemon during the build process.</li>
</ul>
<p>Now to run a container using this image, you can use the <code>container run</code> command coupled with the image ID that you received as the result of the build process. In my case the id is <code>3199372aa3fc</code> evident by the <code>Successfully built 3199372aa3fc</code> line in the previous code block.</p>
<pre><code>docker container run --rm --detach --name custom-nginx-packaged --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> <span class="hljs-number">3199372</span>aa3fc

# ec09d4e1f70c903c3b954c8d7958421cdd1ae3d079b57f929e44131fbf8069a0

docker container ls

# CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
# ec09d4e1f70c        <span class="hljs-number">3199372</span>aa3fc        <span class="hljs-string">"nginx -g 'daemon of…"</span>   <span class="hljs-number">23</span> seconds ago      Up <span class="hljs-number">22</span> seconds       <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   custom-nginx-packaged
</code></pre><p>To verify, visit <code>http://127.0.0.1:8080</code> and you should see the default response page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-tag-docker-images">How to Tag Docker Images</h3>
<p>Just like containers, you can assign custom identifiers to your images instead of relying on the randomly generated ID. In case of an image, it's called tagging instead of naming. The <code>--tag</code> or <code>-t</code> option is used in such cases. </p>
<p>Generic syntax for the option is as follows:</p>
<pre><code>--tag &lt;image repository&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">image</span> <span class="hljs-attr">tag</span>&gt;</span></span>
</code></pre><p>The repository is usually known as the image name and the tag indicates a certain build or version. </p>
<p>Take the official <a target="_blank" href="https://hub.docker.com/_/mysql">mysql</a> image, for example. If you want to run a container using a specific version of MySQL, like 5.7, you can execute <code>docker container run mysql:5.7</code> where <code>mysql</code> is the image repository and <code>5.7</code> is the tag.</p>
<p>In order to tag your custom NGINX image with <code>custom-nginx:packaged</code> you can execute the following command:</p>
<pre><code>docker image build --tag custom-nginx:packaged .

# Sending build context to Docker daemon  <span class="hljs-number">1.055</span>MB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">4</span> : FROM ubuntu:latest
#  ---&gt; f63181f19b2f
# Step <span class="hljs-number">2</span>/<span class="hljs-number">4</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">53</span>ab370b9efc
# Removing intermediate container <span class="hljs-number">53</span>ab370b9efc
#  ---&gt; <span class="hljs-number">6</span>d6460a74447
# Step <span class="hljs-number">3</span>/<span class="hljs-number">4</span> : RUN apt-get update &amp;&amp;     apt-get install nginx -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; Running in b4951b6b48bb
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container b4951b6b48bb
#  ---&gt; fdc6cdd8925a
# Step 4/4 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 3bdbd2af4f0e
# Removing intermediate container 3bdbd2af4f0e
#  ---&gt; f8837621b99d
# Successfully built f8837621b99d
# Successfully tagged custom-nginx:packaged</span>
</code></pre><p>Nothing will change except the fact that you can now refer to your image as <code>custom-nginx:packaged</code> instead of some long random string.</p>
<p>In cases where you forgot to tag an image during build time, or maybe you want to change the tag, you can use the <code>image tag</code> command to do that:</p>
<pre><code>docker image tag &lt;image id&gt; &lt;image repository&gt;:&lt;image tag&gt;

## or ##

docker image tag &lt;image repository&gt;:&lt;image tag&gt; &lt;new image repository&gt;:&lt;new image tag&gt;
</code></pre><h3 id="heading-how-to-list-and-remove-docker-images">How to List and Remove Docker Images</h3>
<p>Just like the <code>container ls</code> command, you can use the <code>image ls</code> command to list all the images in your local system:</p>
<pre><code>docker image ls

# REPOSITORY     TAG        IMAGE ID       CREATED         SIZE
# &lt;none&gt;         <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">none</span>&gt;</span>     3199372aa3fc   7 seconds ago   132MB
# custom-nginx   packaged   f8837621b99d   4 minutes ago   132MB</span>
</code></pre><p>Images listed here can be deleted using the <code>image rm</code> command. The generic syntax is as follows:</p>
<pre><code>docker image rm &lt;image identifier&gt;
</code></pre><p>The identifier can be the image ID or image repository. If you use the repository, you'll have to identify the tag as well. To delete the <code>custom-nginx:packaged</code> image, you may execute the following command:</p>
<pre><code>docker image rm custom-nginx:packaged

# Untagged: custom-nginx:packaged
# Deleted: sha256:f8837621b99d3388a9e78d9ce49fbb773017f770eea80470fb85e0052beae242
# Deleted: sha256:fdc6cdd8925ac25b9e0ed1c8539f96ad89ba1b21793d061e2349b62dd517dadf
# Deleted: sha256:c20e4aa46615fe512a4133089a5cd66f9b7da76366c96548790d5bf865bd49c4
# Deleted: sha256:<span class="hljs-number">6</span>d6460a744475a357a2b631a4098aa1862d04510f3625feb316358536fcd8641
</code></pre><p>You can also use the <code>image prune</code> command to cleanup all un-tagged dangling images as follows:</p>
<pre><code>docker image prune --force

# Deleted Images:
# deleted: sha256:ba9558bdf2beda81b9acc652ce4931a85f0fc7f69dbc91b4efc4561ef7378aff
# deleted: sha256:ad9cc3ff27f0d192f8fa5fadebf813537e02e6ad472f6536847c4de183c02c81
# deleted: sha256:f1e9b82068d43c1bb04ff3e4f0085b9f8903a12b27196df7f1145aa9296c85e7
# deleted: sha256:ec16024aa036172544908ec4e5f842627d04ef99ee9b8d9aaa26b9c2a4b52baa

# Total reclaimed space: <span class="hljs-number">59.19</span>MB
</code></pre><p>The <code>--force</code> or <code>-f</code> option skips any confirmation questions. You can also use the <code>--all</code> or <code>-a</code> option to remove all cached images in your local registry.</p>
<h3 id="heading-how-to-understand-the-many-layers-of-a-docker-image">How to Understand the Many Layers of a Docker Image</h3>
<p>From the very beginning of this book, I've been saying that images are multi-layered files. In this sub-section I'll demonstrate the various layers of an image and how they play an important role in the build process of that image. </p>
<p>For this demonstration, I'll be using the <code>custom-nginx:packaged</code> image from the previous sub-section.</p>
<p>To visualize the many layers of an image, you can use the <code>image history</code> command. The various layers of the <code>custom-nginx:packaged</code> image can be visualized as follows:</p>
<pre><code>docker image history custom-nginx:packaged

# IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
# <span class="hljs-number">7</span>f16387f7307        <span class="hljs-number">5</span> minutes ago       /bin/sh -c #(nop)  CMD [<span class="hljs-string">"nginx"</span> <span class="hljs-string">"-g"</span> <span class="hljs-string">"daemon…   0B                             
# 587c805fe8df        5 minutes ago       /bin/sh -c apt-get update &amp;&amp;     apt-get ins…   60MB                
# 6fe4e51e35c1        6 minutes ago       /bin/sh -c #(nop)  EXPOSE 80                    0B                  
# d70eaf7277ea        17 hours ago        /bin/sh -c #(nop)  CMD ["</span>/bin/bash<span class="hljs-string">"]            0B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c mkdir -p /run/systemd &amp;&amp; echo 'do…   7B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c [ -z "</span>$(apt-get indextargets)<span class="hljs-string">" ]     0B                  
# &lt;missing&gt;           17 hours ago        /bin/sh -c set -xe   &amp;&amp; echo '#!/bin/sh' &gt; /…   811B                
# &lt;missing&gt;           17 hours ago        /bin/sh -c #(nop) ADD file:435d9776fdd3a1834…   72.9MB</span>
</code></pre><p>There are eight layers of this image. The upper most layer is the latest one and as you go down the layers get older. The upper most layer is the one that you usually use for running containers.</p>
<p>Now, let's have a closer look at the images beginning from image <code>d70eaf7277ea</code> down to <code>7f16387f7307</code>. I'll ignore the bottom four layers where the <code>IMAGE</code> is <code>&lt;missing&gt;</code> as they are not of our concern.</p>
<ul>
<li><code>d70eaf7277ea</code> was created by <code>/bin/sh -c #(nop)  CMD ["/bin/bash"]</code> which indicates that the default shell inside Ubuntu has been loaded successfully.</li>
<li><code>6fe4e51e35c1</code> was created by <code>/bin/sh -c #(nop)  EXPOSE 80</code> which was the second instruction in your code.</li>
<li><code>587c805fe8df</code> was created by <code>/bin/sh -c apt-get update &amp;&amp; apt-get install nginx -y &amp;&amp; apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</code> which was the third instruction in your code. You can also see that this image has a size of <code>60MB</code> given all necessary packages were installed during the execution of this instruction.</li>
<li>Finally the upper most layer <code>7f16387f7307</code> was created by <code>/bin/sh -c #(nop)  CMD ["nginx", "-g", "daemon off;"]</code> which sets the default command for this image.</li>
</ul>
<p>As you can see, the image comprises of many read-only layers, each recording a new set of changes to the state triggered by certain instructions. When you start a container using an image, you get a new writable layer on top of the other layers.</p>
<p>This layering phenomenon that happens every time you work with Docker has been made possible by an amazing technical concept called a union file system. Here, union means union in set theory. According to <a target="_blank" href="https://en.wikipedia.org/wiki/UnionFS">Wikipedia</a> - </p>
<blockquote>
<p>It allows files and directories of separate file systems, known as branches, to be transparently overlaid, forming a single coherent file system. Contents of directories which have the same path within the merged branches will be seen together in a single merged directory, within the new, virtual filesystem.</p>
</blockquote>
<p>By utilizing this concept, Docker can avoid data duplication and can use previously created layers as a cache for later builds. This results in compact, efficient images that can be used everywhere.</p>
<h3 id="heading-how-to-build-nginx-from-source">How to Build NGINX from Source</h3>
<p>In the previous sub-section, you learned about the <code>FROM</code>, <code>EXPOSE</code>, <code>RUN</code> and <code>CMD</code> instructions. In this sub-section you'll be learning a lot more about other instructions.</p>
<p>In this sub-section you'll again create a custom NGINX image. But the twist is that you'll be building NGINX from source instead of installing it using some package manager such as <code>apt-get</code> as in the previous example.</p>
<p>In order to build NGINX from source, you first need the source of NGINX. If you've cloned my projects repository you'll see a file named <code>nginx-1.19.2.tar.gz</code> inside the <code>custom-nginx</code> directory. You'll use this archive as the source for building NGINX.</p>
<p>Before diving into writing some code, let's plan out the process first. The image creation process this time can be done in seven steps. These are as follows:</p>
<ul>
<li>Get a good base image for building the application, like <a target="_blank" href="https://hub.docker.com/_/ubuntu">ubuntu</a>.</li>
<li>Install necessary build dependencies on the base image.</li>
<li>Copy the <code>nginx-1.19.2.tar.gz</code> file inside the image.</li>
<li>Extract the contents of the archive and get rid of it.</li>
<li>Configure the build, compile and install the program using the <code>make</code> tool.</li>
<li>Get rid of the extracted source code.</li>
<li>Run <code>nginx</code> executable.</li>
</ul>
<p>Now that you have a plan, let's begin by opening up old <code>Dockerfile</code> and updating its contents as follows:</p>
<pre><code>FROM ubuntu:latest

RUN apt-get update &amp;&amp; \
    apt-get install build-essential\ 
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1<span class="hljs-number">.1</span> \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*

COPY nginx-1.19.2.tar.gz .

RUN tar -xvf nginx-1.19.2.tar.gz &amp;&amp; rm nginx-1.19.2.tar.gz

RUN cd nginx-1.19.2 &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install

RUN rm -rf /nginx-1.19.2

CMD ["nginx", "-g", "daemon off;"]</span>
</code></pre><p>As you can see, the code inside the <code>Dockerfile</code> reflects the seven steps I talked about above.</p>
<ul>
<li>The <code>FROM</code> instruction sets Ubuntu as the base image making an ideal environment for building any application.</li>
<li>The <code>RUN</code> instruction installs standard packages necessary for building NGINX from source.</li>
<li>The <code>COPY</code> instruction here is something new. This instruction is responsible for copying the the <code>nginx-1.19.2.tar.gz</code> file inside the image. The generic syntax for the <code>COPY</code> instruction is <code>COPY &lt;source&gt; &lt;destination&gt;</code> where source is in your local filesystem and the destination is inside your image. The <code>.</code> as the destination means the working directory inside the image which is by default <code>/</code> unless set otherwise.</li>
<li>The second <code>RUN</code> instruction here extracts the contents from the archive using <code>tar</code> and gets rid of it afterwards.</li>
<li>The archive file contains a directory called <code>nginx-1.19.2</code> containing the source code. So on the next step, you'll have to <code>cd</code> inside that directory and perform the build process. You can read the <a target="_blank" href="https://itsfoss.com/install-software-from-source-code/">How to Install Software from Source Code… and Remove it Afterwards</a> article to learn more on the topic.</li>
<li>Once the build and installation is complete, you remove the <code>nginx-1.19.2</code> directory using <code>rm</code> command.</li>
<li>On the final step you start NGINX in single process mode just like you did before.</li>
</ul>
<p>Now to build an image using this code, execute the following command:</p>
<pre><code>docker image build --tag custom-nginx:built .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; Running in 2d0aa912ea47
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container 2d0aa912ea47
#  ---&gt; cbe1ced3da11
# Step 3/7 : COPY nginx-1.19.2.tar.gz .
#  ---&gt; 7202902edf3f
# Step 4/7 : RUN tar -xvf nginx-1.19.2.tar.gz &amp;&amp; rm nginx-1.19.2.tar.gz
 ---&gt; Running in 4a4a95643020
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 4a4a95643020
#  ---&gt; f9dec072d6d6
# Step 5/7 : RUN cd nginx-1.19.2 &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in b07ba12f921e
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container b07ba12f921e
#  ---&gt; 5a877edafd8b
# Step 6/7 : RUN rm -rf /nginx-1.19.2
#  ---&gt; Running in 947e1d9ba828
# Removing intermediate container 947e1d9ba828
#  ---&gt; a7702dc7abb7
# Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 3110c7fdbd57
# Removing intermediate container 3110c7fdbd57
#  ---&gt; eae55f7369d3
# Successfully built eae55f7369d3
# Successfully tagged custom-nginx:built</span>
</code></pre><p>This code is alright but there are some places where we can make improvements.</p>
<ul>
<li>Instead of hard coding the filename like <code>nginx-1.19.2.tar.gz</code>, you can create an argument using the <code>ARG</code> instruction. This way, you'll be able to change the version or filename by just changing the argument.</li>
<li>Instead of downloading the archive manually, you can let the daemon download the file during the build process. There is another instruction like <code>COPY</code> called the <code>ADD</code> instruction which is capable of adding files from the internet.</li>
</ul>
<p>Open up the <code>Dockerfile</code> file and update its content as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install build-essential\ </span>
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.<span class="hljs-number">1</span> \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

<span class="hljs-keyword">ARG</span> FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
<span class="hljs-keyword">ARG</span> EXTENSION=<span class="hljs-string">"tar.gz"</span>

<span class="hljs-keyword">ADD</span><span class="bash"> https://nginx.org/download/<span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> tar -xvf <span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> &amp;&amp; rm <span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span></span>

<span class="hljs-keyword">RUN</span><span class="bash"> <span class="hljs-built_in">cd</span> <span class="hljs-variable">${FILENAME}</span> &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/<span class="hljs-built_in">log</span>/nginx/error.log \
        --http-log-path=/var/<span class="hljs-built_in">log</span>/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install</span>

<span class="hljs-keyword">RUN</span><span class="bash"> rm -rf /<span class="hljs-variable">${FILENAME}</span>}</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>The code is almost identical to the previous code block except for a new instruction called <code>ARG</code> on line 13, 14 and the usage of the <code>ADD</code> instruction on line 16. Explanation for the updated code is as follows:</p>
<ul>
<li>The <code>ARG</code> instruction lets you declare variables like in other languages. These variables or arguments can later be accessed using the <code>${argument name}</code> syntax. Here, I've put the filename <code>nginx-1.19.2</code> and the file extension <code>tar.gz</code> in two separate arguments. This way I can switch between newer versions of NGINX or the archive format by making a change in just one place. In the code above, I've added default values to the variables. Variable values can be passed as options of the <code>image build</code> command as well. You can consult the <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#arg">official reference</a> for more details.</li>
<li>In the <code>ADD</code> instruction, I've formed the download URL dynamically using the arguments declared above. The <code>https://nginx.org/download/${FILENAME}.${EXTENSION}</code> line will result in something like <code>https://nginx.org/download/nginx-1.19.2.tar.gz</code> during the build process. You can change the file version or the extension by changing it in just one place thanks to the <code>ARG</code> instruction.</li>
<li>The <code>ADD</code> instruction doesn't extract files obtained from the internet by default, hence the usage of <code>tar</code> on line 18.</li>
</ul>
<p>The rest of the code is almost unchanged. You should be able to understand the usage of the arguments by yourself now. Finally let's try to build an image from this updated code.</p>
<pre><code>docker image build --tag custom-nginx:built .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">9</span> : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step <span class="hljs-number">2</span>/<span class="hljs-number">9</span> : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; cbe1ced3da11
### LONG INSTALLATION STUFF GOES HERE ###
# Step 3/9 : ARG FILENAME="nginx-1.19.2"
#  ---&gt; Running in 33b62a0e9ffb
# Removing intermediate container 33b62a0e9ffb
#  ---&gt; fafc0aceb9c8
# Step 4/9 : ARG EXTENSION="tar.gz"
#  ---&gt; Running in 5c32eeb1bb11
# Removing intermediate container 5c32eeb1bb11
#  ---&gt; 36efdf6efacc
# Step 5/9 : ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .
# Downloading [==================================================&gt;]  1.049MB/1.049MB
#  ---&gt; dba252f8d609
# Step 6/9 : RUN tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION}
#  ---&gt; Running in 2f5b091b2125
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 2f5b091b2125
#  ---&gt; 2c9a325d74f1
# Step 7/9 : RUN cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in 11cc82dd5186
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container 11cc82dd5186
#  ---&gt; 6c122e485ec8
# Step 8/9 : RUN rm -rf /${FILENAME}}
#  ---&gt; Running in 04102366960b
# Removing intermediate container 04102366960b
#  ---&gt; 6bfa35420a73
# Step 9/9 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 63ee44b571bb
# Removing intermediate container 63ee44b571bb
#  ---&gt; 4ce79556db1b
# Successfully built 4ce79556db1b
# Successfully tagged custom-nginx:built</span>
</code></pre><p>Now you should be able to run a container using the <code>custom-nginx:built</code> image.</p>
<pre><code>docker container run --rm --detach --name custom-nginx-built --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> custom-nginx:built

# <span class="hljs-number">90</span>ccdbc0b598dddc4199451b2f30a942249d85a8ed21da3c8d14612f17eed0aa

docker container ls

# CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                  NAMES
# <span class="hljs-number">90</span>ccdbc0b598        custom-nginx:built   <span class="hljs-string">"nginx -g 'daemon of…"</span>   <span class="hljs-number">2</span> minutes ago       Up <span class="hljs-number">2</span> minutes        <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8080</span>-&gt;<span class="hljs-number">80</span>/tcp   custom-nginx-built
</code></pre><p>A container using the <code>custom-nginx:built-v2</code> image has been successfully run. The container should be accessible at <code>http://127.0.0.1:8080</code> now.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/nginx-default.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And here is the trusty default response page from NGINX. You can visit the <a target="_blank" href="https://docs.docker.com/engine/reference/builder/">official reference</a> site to learn more about the available instructions.</p>
<h3 id="heading-how-to-optimize-docker-images">How to Optimize Docker Images</h3>
<p>The image we built in the last sub-section is functional but very unoptimized. To prove my point let's have a look at the size of the image using the <code>image ls</code> command:</p>
<pre><code>docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
# custom-nginx       built     <span class="hljs-number">1</span>f3aaf40bb54   <span class="hljs-number">16</span> minutes ago   <span class="hljs-number">343</span>MB
</code></pre><p>For an image containing only NGINX, that's too much. If you pull the official image and check its size, you'll see how small it is:</p>
<pre><code>docker image pull nginx:stable

# stable: Pulling <span class="hljs-keyword">from</span> library/nginx
# a076a628af6f: Pull complete 
# <span class="hljs-number">45</span>d7b5d3927d: Pull complete 
# <span class="hljs-number">5e326</span>fece82e: Pull complete 
# <span class="hljs-number">30</span>c386181b68: Pull complete 
# b15158e9ebbe: Pull complete 
# Digest: sha256:ebd0fd56eb30543a9195280eb81af2a9a8e6143496accd6a217c14b06acd1419
# Status: Downloaded newer image <span class="hljs-keyword">for</span> nginx:stable
# docker.io/library/nginx:stable

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
# custom-nginx       built     <span class="hljs-number">1</span>f3aaf40bb54   <span class="hljs-number">25</span> minutes ago   <span class="hljs-number">343</span>MB
# nginx              stable    b9e1dc12387a   <span class="hljs-number">11</span> days ago      <span class="hljs-number">133</span>MB
</code></pre><p>In order to find out the root cause, let's have a look at the <code>Dockerfile</code> first:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install build-essential\ </span>
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.<span class="hljs-number">1</span> \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

<span class="hljs-keyword">ARG</span> FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
<span class="hljs-keyword">ARG</span> EXTENSION=<span class="hljs-string">"tar.gz"</span>

<span class="hljs-keyword">ADD</span><span class="bash"> https://nginx.org/download/<span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> tar -xvf <span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> &amp;&amp; rm <span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span></span>

<span class="hljs-keyword">RUN</span><span class="bash"> <span class="hljs-built_in">cd</span> <span class="hljs-variable">${FILENAME}</span> &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/<span class="hljs-built_in">log</span>/nginx/error.log \
        --http-log-path=/var/<span class="hljs-built_in">log</span>/nginx/access.log \
        --with-pcre \
        --pid-path=/var/run/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install</span>

<span class="hljs-keyword">RUN</span><span class="bash"> rm -rf /<span class="hljs-variable">${FILENAME}</span>}</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>As you can see on line 3, the <code>RUN</code> instruction installs a lot of stuff. Although these packages are necessary for building NGINX from source, they are not necessary for running it. </p>
<p>Out of the 6 packages that we installed, only two are necessary for running NGINX. These are <code>libpcre3</code> and <code>zlib1g</code>. So a better idea would be to uninstall the other packages once the build process is done.</p>
<p>To do so, update your <code>Dockerfile</code> as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">ARG</span> FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
<span class="hljs-keyword">ARG</span> EXTENSION=<span class="hljs-string">"tar.gz"</span>

<span class="hljs-keyword">ADD</span><span class="bash"> https://nginx.org/download/<span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install build-essential \ </span>
                    libpcre3 \
                    libpcre3-dev \
                    zlib1g \
                    zlib1g-dev \
                    libssl1.<span class="hljs-number">1</span> \
                    libssl-dev \
                    -y &amp;&amp; \
    tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp; \
    cd ${FILENAME} &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/<span class="hljs-keyword">run</span><span class="bash">/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install &amp;&amp; \
    <span class="hljs-built_in">cd</span> / &amp;&amp; rm -rfv /<span class="hljs-variable">${FILENAME}</span> &amp;&amp; \
    apt-get remove build-essential \ </span>
                    libpcre3-dev \
                    zlib1g-dev \
                    libssl-dev \
                    -y &amp;&amp; \
    apt-get autoremove -y &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>As you can see, on line 10 a single <code>RUN</code> instruction is doing all the necessary heavy-lifting. The exact chain of events is as follows:</p>
<ul>
<li>From line 10 to line 17, all the necessary packages are being installed.</li>
<li>On line 18, the source code is being extracted and the downloaded archive gets removed.</li>
<li>From line 19 to line 28, NGINX is configured, built, and installed on the system.</li>
<li>On line 29, the extracted files from the downloaded archive get removed.</li>
<li>From line 30 to line 36, all the unnecessary packages are being uninstalled and cache cleared. The <code>libpcre3</code> and <code>zlib1g</code> packages are needed for running NGINX so we keep them.</li>
</ul>
<p>You may ask why am I doing so much work in a single <code>RUN</code> instruction instead of nicely splitting them into multiple instructions like we did previously. Well, splitting them up would be a mistake. </p>
<p>If you install packages and then remove them in separate <code>RUN</code> instructions, they'll live in separate layers of the image. Although the final image will not have the removed packages, their size will still be added to the final image since they exist in one of the layers consisting the image. So make sure you make these kind of changes on a single layer.</p>
<p>Let's build an image using this <code>Dockerfile</code> and see the differences.</p>
<pre><code>docker image build --tag custom-nginx:built .

# Sending build context to Docker daemon  <span class="hljs-number">1.057</span>MB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM ubuntu:latest
#  ---&gt; f63181f19b2f
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">006</span>f39b75964
# Removing intermediate container <span class="hljs-number">006</span>f39b75964
#  ---&gt; <span class="hljs-number">6943</span>f7ef9376
# Step <span class="hljs-number">3</span>/<span class="hljs-number">7</span> : ARG FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> ffaf89078594
# Removing intermediate container ffaf89078594
#  ---&gt; <span class="hljs-number">91</span>b5cdb6dabe
# Step <span class="hljs-number">4</span>/<span class="hljs-number">7</span> : ARG EXTENSION=<span class="hljs-string">"tar.gz"</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> d0f5188444b6
# Removing intermediate container d0f5188444b6
#  ---&gt; <span class="hljs-number">9626</span>f941ccb2
# Step <span class="hljs-number">5</span>/<span class="hljs-number">7</span> : ADD https:<span class="hljs-comment">//nginx.org/download/${FILENAME}.${EXTENSION} .</span>
# Downloading [==================================================&gt;]  <span class="hljs-number">1.049</span>MB/<span class="hljs-number">1.049</span>MB
#  ---&gt; a8e8dcca1be8
# Step <span class="hljs-number">6</span>/<span class="hljs-number">7</span> : RUN apt-get update &amp;&amp;     apt-get install build-essential                     libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp;     cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=<span class="hljs-regexp">/usr/</span>bin/nginx         --conf-path=<span class="hljs-regexp">/etc/</span>nginx/nginx.conf         --error-log-path=<span class="hljs-regexp">/var/</span>log/nginx/error.log         --http-log-path=<span class="hljs-regexp">/var/</span>log/nginx/access.log         --<span class="hljs-keyword">with</span>-pcre         --pid-path=<span class="hljs-regexp">/var/</span>run/nginx.pid         --<span class="hljs-keyword">with</span>-http_ssl_module &amp;&amp;     make &amp;&amp; make install &amp;&amp;     cd / &amp;&amp; rm -rfv /${FILENAME} &amp;&amp;     apt-get remove build-essential                     libpcre3-dev                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get autoremove -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; Running in e5675cad1260
### LONG INSTALLATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container e5675cad1260
#  ---&gt; dc7e4161f975
# Step 7/7 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in b579e4600247
# Removing intermediate container b579e4600247
#  ---&gt; 512aa6a95a93
# Successfully built 512aa6a95a93
# Successfully tagged custom-nginx:built

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED              SIZE
# custom-nginx       built     512aa6a95a93   About a minute ago   81.6MB
# nginx              stable    b9e1dc12387a   11 days ago          133MB</span>
</code></pre><p>As you can see, the image size has gone from being 343MB to 81.6MB. The official image is 133MB. This is a pretty optimized build, but we can go a bit further in the next sub-section.</p>
<h3 id="heading-embracing-alpine-linux">Embracing Alpine Linux</h3>
<p>If you've been fiddling around with containers for some time now, you may have heard about something called <a target="_blank" href="https://alpinelinux.org/">Alpine Linux</a>. It's a full-featured <a target="_blank" href="https://en.wikipedia.org/wiki/Linux">Linux</a> distribution like <a target="_blank" href="https://ubuntu.com/">Ubuntu</a>, <a target="_blank" href="https://www.debian.org/">Debian</a> or <a target="_blank" href="https://getfedora.org/">Fedora</a>. </p>
<p>But the good thing about Alpine is that it's built around <code>musl</code> <code>libc</code> and <code>busybox</code> and is lightweight. Where the latest <a target="_blank" href="https://hub.docker.com/_/ubuntu">ubuntu</a> image weighs at around 28MB, <a target="_blank" href="https://hub.docker.com/_/alpine">alpine</a> is 2.8MB. </p>
<p>Apart from the lightweight nature, Alpine is also secure and is a much better fit for creating containers than the other distributions.</p>
<p>Although not as user friendly as the other commercial distributions, the transition to Alpine is still very simple. In this sub-section you'll learn about recreating the <code>custom-nginx</code> image using the Alpine image as its base.</p>
<p>Open up your <code>Dockerfile</code> and update its content as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> alpine:latest

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">ARG</span> FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
<span class="hljs-keyword">ARG</span> EXTENSION=<span class="hljs-string">"tar.gz"</span>

<span class="hljs-keyword">ADD</span><span class="bash"> https://nginx.org/download/<span class="hljs-variable">${FILENAME}</span>.<span class="hljs-variable">${EXTENSION}</span> .</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apk add --no-cache pcre zlib &amp;&amp; \
    apk add --no-cache \
            --virtual .build-deps \
            build-base \ </span>
            pcre-dev \
            zlib-dev \
            openssl-dev &amp;&amp; \
    tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp; \
    cd ${FILENAME} &amp;&amp; \
    ./configure \
        --sbin-path=/usr/bin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-pcre \
        --pid-path=/var/<span class="hljs-keyword">run</span><span class="bash">/nginx.pid \
        --with-http_ssl_module &amp;&amp; \
    make &amp;&amp; make install &amp;&amp; \
    <span class="hljs-built_in">cd</span> / &amp;&amp; rm -rfv /<span class="hljs-variable">${FILENAME}</span> &amp;&amp; \
    apk del .build-deps</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<p>The code is almost identical except for a few changes. I'll be listing the changes and explaining them as I go:</p>
<ul>
<li>Instead of using <code>apt-get install</code> for installing packages, we use <code>apk add</code>. The <code>--no-cache</code> option means that the downloaded package won't be cached. Likewise we'll use <code>apk del</code> instead of <code>apt-get remove</code> to uninstall packages.</li>
<li>The <code>--virtual</code> option for the <code>apk add</code> command is used for bundling a bunch of packages into a single virtual package for easier management. Packages that are needed only for building the program are labeled as <code>.build-deps</code> which are then removed on line 29 by executing the <code>apk del .build-deps</code> command. You can learn more about <a target="_blank" href="https://docs.alpinelinux.org/user-handbook/0.1a/Working/apk.html#_virtuals">virtuals</a> in the official docs.</li>
<li>The package names are a bit different here. Usually every Linux distribution has its package repository available to everyone where you can search for packages. If you know the packages required for a certain task, then you can just head over to the designated repository for a distribution and search for it. You can <a target="_blank" href="https://pkgs.alpinelinux.org/packages">look up Alpine Linux packages here</a>.</li>
</ul>
<p>Now build a new image using this <code>Dockerfile</code> and see the difference in file size:</p>
<pre><code>docker image build --tag custom-nginx:built .

# Sending build context to Docker daemon  <span class="hljs-number">1.055</span>MB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM alpine:latest
#  ---&gt; <span class="hljs-number">7731472</span>c3f2a
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">8336</span>cfaaa48d
# Removing intermediate container <span class="hljs-number">8336</span>cfaaa48d
#  ---&gt; d448a9049d01
# Step <span class="hljs-number">3</span>/<span class="hljs-number">7</span> : ARG FILENAME=<span class="hljs-string">"nginx-1.19.2"</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> bb8b2eae9d74
# Removing intermediate container bb8b2eae9d74
#  ---&gt; <span class="hljs-number">87</span>ca74f32fbe
# Step <span class="hljs-number">4</span>/<span class="hljs-number">7</span> : ARG EXTENSION=<span class="hljs-string">"tar.gz"</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> aa09627fe48c
# Removing intermediate container aa09627fe48c
#  ---&gt; <span class="hljs-number">70</span>cb557adb10
# Step <span class="hljs-number">5</span>/<span class="hljs-number">7</span> : ADD https:<span class="hljs-comment">//nginx.org/download/${FILENAME}.${EXTENSION} .</span>
# Downloading [==================================================&gt;]  <span class="hljs-number">1.049</span>MB/<span class="hljs-number">1.049</span>MB
#  ---&gt; b9790ce0c4d6
# Step <span class="hljs-number">6</span>/<span class="hljs-number">7</span> : RUN apk add --no-cache pcre zlib &amp;&amp;     apk add --no-cache             --virtual .build-deps             build-base             pcre-dev             zlib-dev             openssl-dev &amp;&amp;     tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION} &amp;&amp;     cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=<span class="hljs-regexp">/usr/</span>bin/nginx         --conf-path=<span class="hljs-regexp">/etc/</span>nginx/nginx.conf         --error-log-path=<span class="hljs-regexp">/var/</span>log/nginx/error.log         --http-log-path=<span class="hljs-regexp">/var/</span>log/nginx/access.log         --<span class="hljs-keyword">with</span>-pcre         --pid-path=<span class="hljs-regexp">/var/</span>run/nginx.pid         --<span class="hljs-keyword">with</span>-http_ssl_module &amp;&amp;     make &amp;&amp; make install &amp;&amp;     cd / &amp;&amp; rm -rfv /${FILENAME} &amp;&amp;     apk del .build-deps
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>b301f64ffc1
### LONG INSTALLATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">0</span>b301f64ffc1
#  ---&gt; dc7e4161f975
# Step <span class="hljs-number">7</span>/<span class="hljs-number">7</span> : CMD [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]
#  ---&gt; Running <span class="hljs-keyword">in</span> b579e4600247
# Removing intermediate container b579e4600247
#  ---&gt; <span class="hljs-number">3e186</span>a3c6830
# Successfully built <span class="hljs-number">3e186</span>a3c6830
# Successfully tagged custom-nginx:built

docker image ls

# REPOSITORY         TAG       IMAGE ID       CREATED         SIZE
# custom-nginx       built     <span class="hljs-number">3e186</span>a3c6830   <span class="hljs-number">8</span> seconds ago   <span class="hljs-number">12.8</span>MB
</code></pre><p>Where the ubuntu version was 81.6MB, the alpine one has come down to 12.8MB which is a massive gain. Apart from the <code>apk</code> package manager, there are some other things that differ in Alpine from Ubuntu but they're not that big a deal. You can just search the internet whenever you get stuck.</p>
<h3 id="heading-how-to-create-executable-docker-images">How to Create Executable Docker Images</h3>
<p>In the previous section you worked with the <a target="_blank" href="https://hub.docker.com/r/fhsinchy/rmbyext">fhsinchy/rmbyext</a> image. In this section you'll learn how to make such an executable image. </p>
<p>To begin with, open up the directory where you've cloned the repository that came with this book. The code for the <code>rmbyext</code> application resides inside the sub-directory with the same name.</p>
<p>Before you start working on the <code>Dockerfile</code> take a moment to plan out what the final output should be. In my opinion it should be like something like this:</p>
<ul>
<li>The image should have Python pre-installed.</li>
<li>It should contain a copy of my <code>rmbyext</code> script.</li>
<li>A working directory should be set where the script will be executed.</li>
<li>The <code>rmbyext</code> script should be set as the entry-point so the image can take extension names as arguments.</li>
</ul>
<p>To build the above mentioned image, take the following steps:</p>
<ul>
<li>Get a good base image for running Python scripts, like <a target="_blank" href="https://hub.docker.com/_/python">python</a>.</li>
<li>Set-up the working directory to an easily accessible directory.</li>
<li>Install Git so that the script can be installed from my GitHub repository.</li>
<li>Install the script using Git and pip.</li>
<li>Get rid of the build's unnecessary packages.</li>
<li>Set <code>rmbyext</code> as the entry-point for this image.</li>
</ul>
<p>Now create a new <code>Dockerfile</code> inside the <code>rmbyext</code> directory and put the following code in it:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3</span>-alpine

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /zone</span>

<span class="hljs-keyword">RUN</span><span class="bash"> apk add --no-cache git &amp;&amp; \
    pip install git+https://github.com/fhsinchy/rmbyext.git<span class="hljs-comment">#egg=rmbyext &amp;&amp; \</span>
    apk del git</span>

<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [ <span class="hljs-string">"rmbyext"</span> ]</span>
</code></pre>
<p>The explanation for the instructions in this file is as follows:</p>
<ul>
<li>The <code>FROM</code> instruction sets <a target="_blank" href="https://hub.docker.com/_/python">python</a> as the base image, making an ideal environment for running Python scripts. The <code>3-alpine</code> tag indicates that you want the Alpine variant of Python 3.</li>
<li>The <code>WORKDIR</code> instruction sets the default working directory to <code>/zone</code> here. The name of the working directory is completely random here. I found zone to be a fitting name, you may use anything you want.</li>
<li>Given the <code>rmbyext</code> script is installed from GitHub, <code>git</code> is an install time dependency. The <code>RUN</code> instruction on line 5 installs <code>git</code> then installs the <code>rmbyext</code> script using Git and pip. It also gets rid of <code>git</code> afterwards.</li>
<li>Finally on line 9, the <code>ENTRYPOINT</code> instruction sets the <code>rmbyext</code> script as the entry-point for this image.</li>
</ul>
<p>In this entire file, line 9 is the magic that turns this seemingly normal image into an executable one. Now to build the image you can execute following command:</p>
<pre><code>docker image build --tag rmbyext .

# Sending build context to Docker daemon  <span class="hljs-number">2.048</span>kB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">4</span> : FROM python:<span class="hljs-number">3</span>-alpine
# <span class="hljs-number">3</span>-alpine: Pulling <span class="hljs-keyword">from</span> library/python
# <span class="hljs-number">801</span>bfaa63ef2: Already exists 
# <span class="hljs-number">8723</span>b2b92bec: Already exists 
# <span class="hljs-number">4e07029</span>ccd64: Already exists 
# <span class="hljs-number">594990504179</span>: Already exists 
# <span class="hljs-number">140</span>d7fec7322: Already exists 
# Digest: sha256:<span class="hljs-number">7492</span>c1f615e3651629bd6c61777e9660caa3819cf3561a47d1d526dfeee02cf6
# Status: Downloaded newer image <span class="hljs-keyword">for</span> python:<span class="hljs-number">3</span>-alpine
#  ---&gt; d4d4f50f871a
# Step <span class="hljs-number">2</span>/<span class="hljs-number">4</span> : WORKDIR /zone
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">454374612</span>a91
# Removing intermediate container <span class="hljs-number">454374612</span>a91
#  ---&gt; <span class="hljs-number">7</span>f7e49bc98d2
# Step <span class="hljs-number">3</span>/<span class="hljs-number">4</span> : RUN apk add --no-cache git &amp;&amp;     pip install git+https:<span class="hljs-comment">//github.com/fhsinchy/rmbyext.git#egg=rmbyext &amp;&amp;     apk del git</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">27e2</span>e96dc95a
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">27e2</span>e96dc95a
#  ---&gt; <span class="hljs-number">3</span>c7389432e36
# Step <span class="hljs-number">4</span>/<span class="hljs-number">4</span> : ENTRYPOINT [ <span class="hljs-string">"rmbyext"</span> ]
#  ---&gt; Running <span class="hljs-keyword">in</span> f239bbea1ca6
# Removing intermediate container f239bbea1ca6
#  ---&gt; <span class="hljs-number">1746</span>b0cedbc7
# Successfully built <span class="hljs-number">1746</span>b0cedbc7
# Successfully tagged rmbyext:latest

docker image ls

# REPOSITORY         TAG        IMAGE ID       CREATED         SIZE
# rmbyext            latest     <span class="hljs-number">1746</span>b0cedbc7   <span class="hljs-number">4</span> minutes ago   <span class="hljs-number">50.9</span>MB
</code></pre><p>Here I haven't provided any tag after the image name, so the image has been tagged as <code>latest</code> by default. You should be able to run the image as you saw in the previous section. Remember to refer to the actual image name you've set, instead of <code>fhsinchy/rmbyext</code> here.</p>
<h3 id="heading-how-to-share-your-docker-images-online">How to Share Your Docker Images Online</h3>
<p>Now that you know how to make images, it's time to share them with the world. Sharing images online is easy. All you need is an account at any of the online registries. I'll be using <a target="_blank" href="https://hub.docker.com/">Docker Hub</a> here. </p>
<p>Navigate to the <a target="_blank" href="https://hub.docker.com/signup">Sign Up</a> page and create a free account. A free account allows you to host unlimited public repositories and one private repository.</p>
<p>Once you've created the account, you'll have to sign in to it using the docker CLI. So open up your terminal and execute the following command to do so:</p>
<pre><code>docker login

# Login <span class="hljs-keyword">with</span> your Docker ID to push and pull images <span class="hljs-keyword">from</span> Docker Hub. If you don<span class="hljs-string">'t have a Docker ID, head over to https://hub.docker.com to create one.
# Username: fhsinchy
# Password: 
# WARNING! Your password will be stored unencrypted in /home/fhsinchy/.docker/config.json.
# Configure a credential helper to remove this warning. See
# https://docs.docker.com/engine/reference/commandline/login/#credentials-store
#
# Login Succeeded</span>
</code></pre><p>You'll be prompted for your username and password. If you input them properly, you should be logged in to your account successfully.</p>
<p>In order to share an image online, the image has to be tagged. You've already learned about tagging in a previous sub-section. Just to refresh your memory, the generic syntax for the <code>--tag</code> or <code>-t</code> option is as follows:</p>
<pre><code>--tag &lt;image repository&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">image</span> <span class="hljs-attr">tag</span>&gt;</span></span>
</code></pre><p>As an example, let's share the <code>custom-nginx</code> image online. To do so, open up a new terminal window inside the <code>custom-nginx</code> project directory. </p>
<p>To share an image online, you'll have to tag it following the <code>&lt;docker hub username&gt;/&lt;image name&gt;:&lt;image tag&gt;</code> syntax. My username is <code>fhsinchy</code> so the command will look like this:</p>
<pre><code>docker image build --tag fhsinchy/custom-nginx:latest --file Dockerfile.built .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">9</span> : FROM ubuntu:latest
#  ---&gt; d70eaf7277ea
# Step <span class="hljs-number">2</span>/<span class="hljs-number">9</span> : RUN apt-get update &amp;&amp;     apt-get install build-essential                    libpcre3                     libpcre3-dev                     zlib1g                     zlib1g-dev                     libssl-dev                     -y &amp;&amp;     apt-get clean &amp;&amp; rm -rf /<span class="hljs-keyword">var</span>/lib/apt/lists<span class="hljs-comment">/*
#  ---&gt; cbe1ced3da11
### LONG INSTALLATION STUFF GOES HERE ###
# Step 3/9 : ARG FILENAME="nginx-1.19.2"
#  ---&gt; Running in 33b62a0e9ffb
# Removing intermediate container 33b62a0e9ffb
#  ---&gt; fafc0aceb9c8
# Step 4/9 : ARG EXTENSION="tar.gz"
#  ---&gt; Running in 5c32eeb1bb11
# Removing intermediate container 5c32eeb1bb11
#  ---&gt; 36efdf6efacc
# Step 5/9 : ADD https://nginx.org/download/${FILENAME}.${EXTENSION} .
# Downloading [==================================================&gt;]  1.049MB/1.049MB
#  ---&gt; dba252f8d609
# Step 6/9 : RUN tar -xvf ${FILENAME}.${EXTENSION} &amp;&amp; rm ${FILENAME}.${EXTENSION}
#  ---&gt; Running in 2f5b091b2125
### LONG EXTRACTION STUFF GOES HERE ###
# Removing intermediate container 2f5b091b2125
#  ---&gt; 2c9a325d74f1
# Step 7/9 : RUN cd ${FILENAME} &amp;&amp;     ./configure         --sbin-path=/usr/bin/nginx         --conf-path=/etc/nginx/nginx.conf         --error-log-path=/var/log/nginx/error.log         --http-log-path=/var/log/nginx/access.log         --with-pcre         --pid-path=/var/run/nginx.pid         --with-http_ssl_module &amp;&amp;     make &amp;&amp; make install
#  ---&gt; Running in 11cc82dd5186
### LONG CONFIGURATION AND BUILD STUFF GOES HERE ###
# Removing intermediate container 11cc82dd5186
#  ---&gt; 6c122e485ec8
# Step 8/9 : RUN rm -rf /${FILENAME}}
#  ---&gt; Running in 04102366960b
# Removing intermediate container 04102366960b
#  ---&gt; 6bfa35420a73
# Step 9/9 : CMD ["nginx", "-g", "daemon off;"]
#  ---&gt; Running in 63ee44b571bb
# Removing intermediate container 63ee44b571bb
#  ---&gt; 4ce79556db1b
# Successfully built 4ce79556db1b
# Successfully tagged fhsinchy/custom-nginx:latest</span>
</code></pre><p>In this command the <code>fhsinchy/custom-nginx</code> is the image repository and <code>latest</code> is the tag. The image name can be anything you want and can not be changed once you've uploaded the image. The tag can be changed whenever you want and usually reflects the version of the software or different kind of builds.</p>
<p>Take the <code>node</code> image as an example. The <code>node:lts</code> image refers to the long term support version of Node.js whereas the <code>node:lts-alpine</code> version refers to the Node.js version built for Alpine Linux, which is much smaller than the regular one.</p>
<p>If you do not give the image any tag, it'll be automatically tagged as <code>latest</code>. But that doesn't mean that the <code>latest</code> tag will always refer to the latest version. If, for some reason, you explicitly tag an older version of the image as <code>latest</code>, then Docker will not make any extra effort to cross check that.</p>
<p>Once the image has been built, you can them upload it by executing the following command:</p>
<pre><code>docker image push &lt;image repository&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">image</span> <span class="hljs-attr">tag</span>&gt;</span></span>
</code></pre><p>So in my case the command will be as follows:</p>
<pre><code>docker image push fhsinchy/custom-nginx:latest

# The push refers to repository [docker.io/fhsinchy/custom-nginx]
# <span class="hljs-number">4352</span>b1b1d9f5: Pushed 
# a4518dd720bd: Pushed 
# <span class="hljs-number">1</span>d756dc4e694: Pushed 
# d7a7e2b6321a: Pushed 
# f6253634dc78: Mounted <span class="hljs-keyword">from</span> library/ubuntu 
# <span class="hljs-number">9069</span>f84dbbe9: Mounted <span class="hljs-keyword">from</span> library/ubuntu 
# bacd3af13903: Mounted <span class="hljs-keyword">from</span> library/ubuntu 
# latest: digest: sha256:ffe93440256c9edb2ed67bf3bba3c204fec3a46a36ac53358899ce1a9eee497a size: <span class="hljs-number">1788</span>
</code></pre><p>Depending on the image size, the upload may take some time. Once it's done you should able to find the image in your hub profile page.</p>
<h2 id="heading-how-to-containerize-a-javascript-application">How to Containerize a JavaScript Application</h2>
<p>Now that you've got some idea of how to create images, it's time to work with something a bit more relevant. </p>
<p>In this sub-section, you'll be working with the source code of the <a target="_blank" href="https://hub.docker.com/r/fhsinchy/hello-dock">fhsinchy/hello-dock</a> image that you worked with on a previous section. In the process of containerizing this very simple application, you'll be introduced to volumes and multi-staged builds, two of the most important concepts in Docker.</p>
<h3 id="heading-how-to-write-the-development-dockerfile">How to Write the Development Dockerfile</h3>
<p>To begin with, open up the directory where you've cloned the repository that came with this book. Code for the <code>hello-dock</code> application resides inside the sub-directory with the same name.</p>
<p>This is a very simple JavaScript project powered by the <a target="_blank" href="https://github.com/vitejs/vite">vitejs/vite</a> project. Don't worry though, you don't need to know JavaScript or vite in order to go through this sub-section. Having a basic understanding of <a target="_blank" href="https://nodejs.org/">Node.js</a> and <a target="_blank" href="https://www.npmjs.com/">npm</a> will suffice.</p>
<p>Just like any other project you've done in the previous sub-section, you'll begin by making a plan of how you want this application to run. In my opinion, the plan should be as follows:</p>
<ul>
<li>Get a good base image for running JavaScript applications, like <a target="_blank" href="https://hub.docker.com/_/node">node</a>.</li>
<li>Set the default working directory inside the image.</li>
<li>Copy the <code>package.json</code> file into the image.</li>
<li>Install necessary dependencies.</li>
<li>Copy the rest of the project files.</li>
<li>Start the <code>vite</code> development server by executing <code>npm run dev</code> command.</li>
</ul>
<p>This plan should always come from the developer of the application that you're containerizing. If you're the developer yourself, then you should already have a proper understanding of how this application needs to be run. </p>
<p>Now if you put the above mentioned plan inside <code>Dockerfile.dev</code>, the file should look like as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:lts-alpine

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>

<span class="hljs-keyword">USER</span> node

<span class="hljs-keyword">RUN</span><span class="bash"> mkdir -p /home/node/app</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /home/node/app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> ./package.json .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [ <span class="hljs-string">"npm"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"dev"</span> ]</span>
</code></pre>
<p>The explanation for this code is as follows:</p>
<ul>
<li>The <code>FROM</code> instruction here sets the official Node.js image as the base, giving you all the goodness of Node.js necessary to run any JavaScript application. The <code>lts-alpine</code> tag indicates that you want to use the Alpine variant, long term support version of the image. Available tags and necessary documentation for the image can be found on the <a target="_blank" href="https://hub.docker.com/_/node">node</a> hub page.</li>
<li>The <code>USER</code> instruction sets the default user for the image to <code>node</code>. By default Docker runs containers as the root user. But according to <a target="_blank" href="https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md">Docker and Node.js Best Practices</a> this can pose a security threat. So it's a better idea to run as a non-root user whenever possible. The node image comes with a non-root user named <code>node</code> which you can set as the default user using the <code>USER</code> instruction.</li>
<li>The <code>RUN mkdir -p /home/node/app</code> instruction creates a directory called <code>app</code> inside the home directory of the <code>node</code> user. The home directory for any non-root user in Linux is usually <code>/home/&lt;user name&gt;</code> by default.</li>
<li>Then the <code>WORKDIR</code> instruction sets the default working directory to the newly created <code>/home/node/app</code> directory. By default the working directory of any image is the root. You don't want any unnecessary files sprayed all over your root directory, do you? Hence you change the default working directory to something more sensible like <code>/home/node/app</code> or whatever you like. This working directory will be applicable to any subsequent <code>COPY</code>, <code>ADD</code>, <code>RUN</code> and <code>CMD</code> instructions.</li>
<li>The <code>COPY</code> instruction here copies the <code>package.json</code> file which contains information regarding all the necessary dependencies for this application. The <code>RUN</code> instruction executes the <code>npm install</code> command which is the default command for installing dependencies using a <code>package.json</code> file in Node.js projects. The <code>.</code> at the end represents the working directory.</li>
<li>The second <code>COPY</code> instruction copies the rest of the content from the current directory (<code>.</code>) of the host filesystem to the working directory (<code>.</code>) inside the image.</li>
<li>Finally, the <code>CMD</code> instruction here sets the default command for this image which is <code>npm run dev</code> written in <code>exec</code> form.</li>
<li>The <code>vite</code> development server by default runs on port <code>3000</code> , and adding an <code>EXPOSE</code> command seemed like a good idea, so there you go.</li>
</ul>
<p>Now, to build an image from this <code>Dockerfile.dev</code> you can execute the following command:</p>
<pre><code>docker image build --file Dockerfile.dev --tag hello-dock:dev .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM node:lts
#  ---&gt; b90fa0d7cbd1
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : EXPOSE <span class="hljs-number">3000</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">722</span>d639badc7
# Removing intermediate container <span class="hljs-number">722</span>d639badc7
#  ---&gt; e2a8aa88790e
# Step <span class="hljs-number">3</span>/<span class="hljs-number">7</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">998e254</span>b4d22
# Removing intermediate container <span class="hljs-number">998e254</span>b4d22
#  ---&gt; <span class="hljs-number">6</span>bd4c42892a4
# Step <span class="hljs-number">4</span>/<span class="hljs-number">7</span> : COPY ./package.json .
#  ---&gt; <span class="hljs-number">24</span>fc5164a1dc
# Step <span class="hljs-number">5</span>/<span class="hljs-number">7</span> : RUN npm install
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">23</span>b4de3f930b
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">23</span>b4de3f930b
#  ---&gt; c17ecb19a210
# Step <span class="hljs-number">6</span>/<span class="hljs-number">7</span> : COPY . .
#  ---&gt; afb6d9a1bc76
# Step <span class="hljs-number">7</span>/<span class="hljs-number">7</span> : CMD [ <span class="hljs-string">"npm"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"dev"</span> ]
#  ---&gt; Running <span class="hljs-keyword">in</span> a7ff529c28fe
# Removing intermediate container a7ff529c28fe
#  ---&gt; <span class="hljs-number">1792250</span>adb79
# Successfully built <span class="hljs-number">1792250</span>adb79
# Successfully tagged hello-dock:dev
</code></pre><p>Given the filename is not <code>Dockerfile</code> you have to explicitly pass the filename using the <code>--file</code> option. A container can be run using this image by executing the following command:</p>
<pre><code>docker container run \
    --rm \
    --detach \
    --publish <span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> \
    --name hello-dock-dev \
    hello-dock:dev

# <span class="hljs-number">21</span>b9b1499d195d85e81f0e8bce08f43a64b63d589c5f15cbbd0b9c0cb07ae268
</code></pre><p>Now visit <code>http://127.0.0.1:3000</code> to see the <code>hello-dock</code> application in action.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock-dev.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Congratulations on running your first real-world application inside a container. The code you've just written is okay but there is one big issue with it and a few places where it can be improved. Let's begin with the issue first.</p>
<h3 id="heading-how-to-work-with-bind-mounts-in-docker">How to Work With Bind Mounts in Docker</h3>
<p>If you've worked with any front-end JavaScript framework before, you should know that the development servers in these frameworks usually come with a hot reload feature. That is if you make a change in your code, the server will reload, automatically reflecting any changes you've made immediately.</p>
<p>But if you make any changes in your code right now, you'll see nothing happening to your application running in the browser. This is because you're making changes in the code that you have in your local file system but the application you're seeing in the browser resides inside the container file system.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/local-vs-container-file-system.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To solve this issue, you can again make use of a <a target="_blank" href="https://docs.docker.com/storage/bind-mounts/">bind mount</a>. Using bind mounts, you can easily mount one of your local file system directories inside a container. Instead of making a copy of the local file system, the bind mount can reference the local file system directly from inside the container.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/bind-mounts.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This way, any changes you make to your local source code will reflect immediately inside the container,  triggering the hot reload feature of the <code>vite</code> development server. Changes made to the file system inside the container will be reflected on your local file system as well.</p>
<p>You've already learned in the <a class="post-section-overview" href="#working-with-executable-images">Working With Executable Images</a> sub-section, bind mounts can be created using the <code>--volume</code> or <code>-v</code> option for the <code>container run</code> or <code>container start</code> commands. Just to remind you, the generic syntax is as follows:</p>
<pre><code>--volume &lt;local file system directory absolute path&gt;:&lt;container file system directory absolute path&gt;:&lt;read write access&gt;
</code></pre><p>Stop your previously started <code>hello-dock-dev</code> container, and start a new container by executing the following command:</p>
<pre><code>docker container run \
    --rm \
    --publish <span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> \
    --name hello-dock-dev \
    --volume $(pwd):<span class="hljs-regexp">/home/</span>node/app \
    hello-dock:dev

# sh: <span class="hljs-number">1</span>: vite: not found
# npm ERR! code ELIFECYCLE
# npm ERR! syscall spawn
# npm ERR! file sh
# npm ERR! errno ENOENT
# npm ERR! hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> dev: <span class="hljs-string">`vite`</span>
# npm ERR! spawn ENOENT
# npm ERR!
# npm ERR! Failed at the hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> dev script.
# npm ERR! This is probably not a problem <span class="hljs-keyword">with</span> npm. There is likely additional logging output above.
# npm WARN Local package.json exists, but node_modules missing, did you mean to install?
</code></pre><p>Keep in mind, I've omitted the <code>--detach</code> option and that's to demonstrate a very important point. As you can see, the application is not running at all now.</p>
<p>That's because although the usage of a volume solves the issue of hot reloads, it introduces another problem. If you have any previous experience with Node.js, you may know that the dependencies of a Node.js project live inside the <code>node_modules</code> directory on the project root.</p>
<p>Now that you're mounting the project root on your local file system as a volume inside the container, the content inside the container gets replaced along with the <code>node_modules</code> directory containing all the dependencies. This means that the <code>vite</code> package has gone missing.</p>
<h3 id="heading-how-to-work-with-anonymous-volumes-in-docker">How to Work With Anonymous Volumes in Docker</h3>
<p>This problem can be solved using an anonymous volume. An anonymous volume is identical to a bind mount except that you don't need to specify the source directory here. The generic syntax for creating an anonymous volume is as follows:</p>
<pre><code>--volume &lt;container file system directory absolute path&gt;:<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">read</span> <span class="hljs-attr">write</span> <span class="hljs-attr">access</span>&gt;</span></span>
</code></pre><p>So the final command for starting the <code>hello-dock</code> container with both volumes should be as follows:</p>
<pre><code>docker container run \
    --rm \
    --detach \
    --publish <span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> \
    --name hello-dock-dev \
    --volume $(pwd):<span class="hljs-regexp">/home/</span>node/app \
    --volume /home/node/app/node_modules \
    hello-dock:dev

# <span class="hljs-number">53</span>d1cfdb3ef148eb6370e338749836160f75f076d0fbec3c2a9b059a8992de8b
</code></pre><p>Here, Docker will take the entire <code>node_modules</code> directory from inside the container and tuck it away in some other directory managed by the Docker daemon on your host file system and will mount that directory as <code>node_modules</code> inside the container.</p>
<h3 id="heading-how-to-perform-multi-staged-builds-in-docker">How to Perform Multi-Staged Builds in Docker</h3>
<p>So far in this section, you've built an image for running a JavaScript application in development mode. Now if you want to build the image in production mode, some new challenges show up. </p>
<p>In development mode the <code>npm run serve</code> command starts a development server that serves the application to the user. That server not only serves the files but also provides the hot reload feature.</p>
<p>In production mode, the <code>npm run build</code> command compiles all your JavaScript code into some static HTML, CSS, and JavaScript files. To run these files you don't need node or any other runtime dependencies. All you need is a server like <code>nginx</code> for example.</p>
<p>To create an image where the application runs in production mode, you can take the following steps:</p>
<ul>
<li>Use <code>node</code> as the base image and build the application.</li>
<li>Install <code>nginx</code> inside the node image and use that to serve the static files.</li>
</ul>
<p>This approach is completely valid. But the problem is that the <code>node</code> image is big and most of the stuff it carries is unnecessary to serve your static files. A better approach to this scenario is as follows:</p>
<ul>
<li>Use <code>node</code> image as the base and build the application.</li>
<li>Copy the files created using the <code>node</code> image to an <code>nginx</code> image.</li>
<li>Create the final image based on <code>nginx</code> and discard all <code>node</code> related stuff.</li>
</ul>
<p>This way your image only contains the files that are needed and becomes really handy. </p>
<p>This approach is a multi-staged build. To perform such a build, create a new <code>Dockerfile</code> inside your <code>hello-dock</code> project directory and put the following content in it:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:lts-alpine as builder

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> ./package.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build</span>

<span class="hljs-keyword">FROM</span> nginx:stable-alpine

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/dist /usr/share/nginx/html</span>
</code></pre>
<p>As you can see the <code>Dockerfile</code> looks a lot like your previous ones with a few oddities. The explanation for this file is as follows:</p>
<ul>
<li>Line 1 starts the first stage of the build using <code>node:lts-alpine</code> as the base image. The <code>as builder</code> syntax assigns a name to this stage so that it can be referred to later on.</li>
<li>From line 3 to line 9, it's standard stuff that you've seen many times before. The <code>RUN npm run build</code> command actually compiles the entire application and tucks it inside <code>/app/dist</code> directory where <code>/app</code> is the working directory and <code>/dist</code> is the default output directory for <code>vite</code> applications.</li>
<li>Line 11 starts the second stage of the build using <code>nginx:stable-alpine</code> as the base image.</li>
<li>The NGINX server runs on port 80 by default so the line <code>EXPOSE 80</code> is added.</li>
<li>The last line is a <code>COPY</code> instruction. The <code>--from=builder</code> part indicates that you want to copy some files from the <code>builder</code> stage. After that it's a standard copy instruction where <code>/app/dist</code> is the source and <code>/usr/share/nginx/html</code> is the destination. The destination used here is the default site path for NGINX so any static file you put inside there will be automatically served.</li>
</ul>
<p>As you can see, the resulting image is a <code>nginx</code> base image containing only the files necessary for running the application. To build this image execute the following command:</p>
<pre><code>docker image build --tag hello-dock:prod .

# Step <span class="hljs-number">1</span>/<span class="hljs-number">9</span> : FROM node:lts-alpine <span class="hljs-keyword">as</span> builder
#  ---&gt; <span class="hljs-number">72</span>aaced1868f
# Step <span class="hljs-number">2</span>/<span class="hljs-number">9</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> e361c5c866dd
# Removing intermediate container e361c5c866dd
#  ---&gt; <span class="hljs-number">241</span>b4b97b34c
# Step <span class="hljs-number">3</span>/<span class="hljs-number">9</span> : COPY ./package.json ./
#  ---&gt; <span class="hljs-number">6</span>c594c5d2300
# Step <span class="hljs-number">4</span>/<span class="hljs-number">9</span> : RUN npm install
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">6</span>dfabf0ee9f8
# npm WARN deprecated fsevents@<span class="hljs-number">2.1</span><span class="hljs-number">.3</span>: Please update to v <span class="hljs-number">2.2</span>.x
#
# &gt; esbuild@<span class="hljs-number">0.8</span><span class="hljs-number">.29</span> postinstall /app/node_modules/esbuild
# &gt; node install.js
#
# npm notice created a lockfile <span class="hljs-keyword">as</span> package-lock.json. You should commit <span class="hljs-built_in">this</span> file.
# npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@~<span class="hljs-number">2.1</span><span class="hljs-number">.2</span> (node_modules/chokidar/node_modules/fsevents):
# npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform <span class="hljs-keyword">for</span> fsevents@<span class="hljs-number">2.1</span><span class="hljs-number">.3</span>: wanted {<span class="hljs-string">"os"</span>:<span class="hljs-string">"darwin"</span>,<span class="hljs-string">"arch"</span>:<span class="hljs-string">"any"</span>} (current: {<span class="hljs-string">"os"</span>:<span class="hljs-string">"linux"</span>,<span class="hljs-string">"arch"</span>:<span class="hljs-string">"x64"</span>})
# npm WARN hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> No description
# npm WARN hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> No repository field.
# npm WARN hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> No license field.
#
# added <span class="hljs-number">327</span> packages <span class="hljs-keyword">from</span> <span class="hljs-number">301</span> contributors and audited <span class="hljs-number">329</span> packages <span class="hljs-keyword">in</span> <span class="hljs-number">35.971</span>s
#
# <span class="hljs-number">26</span> packages are looking <span class="hljs-keyword">for</span> funding
#   run <span class="hljs-string">`npm fund`</span> <span class="hljs-keyword">for</span> details
#
# found <span class="hljs-number">0</span> vulnerabilities
#
# Removing intermediate container <span class="hljs-number">6</span>dfabf0ee9f8
#  ---&gt; <span class="hljs-number">21</span>fd1b065314
# Step <span class="hljs-number">5</span>/<span class="hljs-number">9</span> : COPY . .
#  ---&gt; <span class="hljs-number">43243</span>f95bff7
# Step <span class="hljs-number">6</span>/<span class="hljs-number">9</span> : RUN npm run build
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">4</span>d918cf18584
#
# &gt; hello-dock@<span class="hljs-number">0.0</span><span class="hljs-number">.0</span> build /app
# &gt; vite build
#
# - Building production bundle...
#
# [write] dist/index.html <span class="hljs-number">0.39</span>kb, <span class="hljs-attr">brotli</span>: <span class="hljs-number">0.15</span>kb
# [write] dist/_assets/docker-handbook-github<span class="hljs-number">.3</span>adb4865.webp <span class="hljs-number">12.32</span>kb
# [write] dist/_assets/index.eabcae90.js <span class="hljs-number">42.56</span>kb, <span class="hljs-attr">brotli</span>: <span class="hljs-number">15.40</span>kb
# [write] dist/_assets/style<span class="hljs-number">.0637</span>ccc5.css <span class="hljs-number">0.16</span>kb, <span class="hljs-attr">brotli</span>: <span class="hljs-number">0.10</span>kb
# - Building production bundle...
#
# Build completed <span class="hljs-keyword">in</span> <span class="hljs-number">1.71</span>s.
#
# Removing intermediate container <span class="hljs-number">4</span>d918cf18584
#  ---&gt; <span class="hljs-number">187</span>fb3e82d0d
# Step <span class="hljs-number">7</span>/<span class="hljs-number">9</span> : EXPOSE <span class="hljs-number">80</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> b3aab5cf5975
# Removing intermediate container b3aab5cf5975
#  ---&gt; d6fcc058cfda
# Step <span class="hljs-number">8</span>/<span class="hljs-number">9</span> : FROM nginx:stable-alpine
# stable: Pulling <span class="hljs-keyword">from</span> library/nginx
# <span class="hljs-number">6</span>ec7b7d162b2: Already exists 
# <span class="hljs-number">43876</span>acb2da3: Pull complete 
# <span class="hljs-number">7</span>a79edd1e27b: Pull complete 
# eea03077c87e: Pull complete 
# eba7631b45c5: Pull complete 
# Digest: sha256:<span class="hljs-number">2</span>eea9f5d6fff078ad6cc6c961ab11b8314efd91fb8480b5d054c7057a619e0c3
# Status: Downloaded newer image <span class="hljs-keyword">for</span> nginx:stable
#  ---&gt; <span class="hljs-number">05</span>f64a802c26
# Step <span class="hljs-number">9</span>/<span class="hljs-number">9</span> : COPY --<span class="hljs-keyword">from</span>=builder /app/dist /usr/share/nginx/html
#  ---&gt; <span class="hljs-number">8</span>c6dfc34a10d
# Successfully built <span class="hljs-number">8</span>c6dfc34a10d
# Successfully tagged hello-dock:prod
</code></pre><p>Once the image has been built, you may run a new container by executing the following command:</p>
<pre><code>docker container run \
    --rm \
    --detach \
    --name hello-dock-prod \
    --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> \
    hello-dock:prod

# <span class="hljs-number">224</span>aaba432bb09aca518fdd0365875895c2f5121eb668b2e7b2d5a99c019b953
</code></pre><p>The running application should be available on <code>http://127.0.0.1:8080</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/hello-dock.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here you can see my <code>hello-dock</code> application in all its glory. Multi-staged builds can be very useful if you're building large applications with a lot of dependencies. If configured properly, images built in multiple stages can be very optimized and compact.</p>
<h3 id="heading-how-to-ignore-unnecessary-files">How to Ignore Unnecessary Files</h3>
<p>If you've been working with <code>git</code> for some time now, you may know about the <code>.gitignore</code> files in projects. These contain a list of files and directories to be excluded from the repository. </p>
<p>Well, Docker has a similar concept. The <code>.dockerignore</code> file contains a list of files and directories to be excluded from image builds. You can find a pre-created <code>.dockerignore</code> file in the <code>hello-dock</code> directory.</p>
<pre><code class="lang-dockerignore">.git
*Dockerfile*
*docker-compose*
node_modules
</code></pre>
<p>This <code>.dockerignore</code> file has to be in the build context. Files and directories mentioned here will be ignored by the <code>COPY</code> instruction. But if you do a bind mount, the <code>.dockerignore</code> file will have no effect. I've added <code>.dockerignore</code> files where necessary in the project repository.</p>
<h2 id="heading-network-manipulation-basics-in-docker">Network Manipulation Basics in Docker</h2>
<p>So far in this book, you've only worked with single container projects. But in real life, the majority of projects that you'll have to work with will have more than one container. And to be honest, working with a bunch of containers can be a little difficult if you don't understand the nuances of container isolation. </p>
<p>So in this section of the book, you'll get familiar with basic networking with Docker and you'll work hands on with a small multi-container project.</p>
<p>Well you've already learned in the previous section that containers are isolated environments. Now consider a scenario where you have a <code>notes-api</code> application powered by <a target="_blank" href="https://expressjs.com/">Express.js</a> and a <a target="_blank" href="https://www.postgresql.org/">PostgreSQL</a> database server running in two separate containers.</p>
<p>These two containers are completely isolated from each other and are oblivious to each other's existence. <strong>So how do you connect the two? Won't that be a challenge?</strong>‌</p>
<p>You may think of two possible solutions to this problem. They are as follows:</p>
<ul>
<li>Accessing the database server using an exposed port.</li>
<li>Accessing the database server using its IP address and default port.</li>
</ul>
<p>The first one involves exposing a port from the <code>postgres</code> container and the <code>notes-api</code> will connect through that. Assume that the exposed port from the <code>postgres</code> container is 5432. Now if you try to connect to <code>127.0.0.1:5432</code> from inside the <code>notes-api</code> container, you'll find that the <code>notes-api</code> can't find the database server at all.</p>
<p>The reason is that when you're saying <code>127.0.0.1</code> inside the <code>notes-api</code> container, you're simply referring to the <code>localhost</code> of that container and that container only. The <code>postgres</code> server simply doesn't exist there. As a result the <code>notes-api</code> application failed to connect.</p>
<p>The second solution you may think of is finding the exact IP address of the <code>postgres</code> container using the <code>container inspect</code> command and using that with the port. Assuming the name of the <code>postgres</code> container is <code>notes-api-db-server</code> you can easily get the IP address by executing the following command:</p>
<pre><code>docker container inspect --format=<span class="hljs-string">'{{range .NetworkSettings.Networks}} {{.IPAddress}} {{end}}'</span> notes-api-db-server

#  <span class="hljs-number">172.17</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>
</code></pre><p>Now given that the default port for <code>postgres</code> is <code>5432</code>, you can very easily access the database server by connecting to <code>172.17.0.2:5432</code> from the <code>notes-api</code> container.</p>
<p>There are problems in this approach as well. Using IP addresses to refer to a container is not recommended. Also, if the container gets destroyed and recreated, the IP address may change. Keeping track of these changing IP addresses can be pretty hectic.</p>
<p>Now that I've dismissed the possible wrong answers to the original question, the correct answer is, <strong>you connect them by putting them under a user-defined bridge network.</strong></p>
<h3 id="heading-docker-network-basics">Docker Network Basics</h3>
<p>A network in Docker is another logical object like a container and image. Just like the other two, there is a plethora of commands under the <code>docker network</code> group for manipulating networks. </p>
<p>To list out the networks in your system, execute the following command:</p>
<pre><code>docker network ls

# NETWORK ID     NAME      DRIVER    SCOPE
# c2e59f2b96bd   bridge    bridge    local
# <span class="hljs-number">124</span>dccee067f   host      host      local
# <span class="hljs-number">506e3822</span>bf1f   none      <span class="hljs-literal">null</span>      local
</code></pre><p>You should see three networks in your system. Now look at the <code>DRIVER</code> column of the table here. These drivers are can be treated as the type of network. </p>
<p>By default, Docker has five networking drivers. They are as follows:</p>
<ul>
<li><code>bridge</code> - The default networking driver in Docker. This can be used when multiple containers are running in standard mode and need to communicate with each other.</li>
<li><code>host</code> - Removes the network isolation completely. Any container running under a <code>host</code> network is basically attached to the network of the host system.</li>
<li><code>none</code> - This driver disables networking for containers altogether. I haven't found any use-case for this yet.</li>
<li><code>overlay</code> - This is used for connecting multiple Docker daemons across computers and is out of the scope of this book.</li>
<li><code>macvlan</code> - Allows assignment of MAC addresses to containers, making them function like physical devices in a network.</li>
</ul>
<p>There are also third-party plugins that allow you to integrate Docker with specialized network stacks. Out of the five mentioned above, you'll only work with the <code>bridge</code> networking driver in this book.</p>
<h3 id="heading-how-to-create-a-user-defined-bridge-in-docker">How to Create a User-Defined Bridge in Docker</h3>
<p>Before you start creating your own bridge, I would like to take some time to discuss the default bridge network that comes with Docker. Let's begin by listing all the networks on your system:</p>
<pre><code>docker network ls

# NETWORK ID     NAME      DRIVER    SCOPE
# c2e59f2b96bd   bridge    bridge    local
# <span class="hljs-number">124</span>dccee067f   host      host      local
# <span class="hljs-number">506e3822</span>bf1f   none      <span class="hljs-literal">null</span>      local
</code></pre><p>As you can see, Docker comes with a default bridge network named <code>bridge</code>. Any container you run will be automatically attached to this bridge network:</p>
<pre><code>docker container run --rm --detach --name hello-dock --publish <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> fhsinchy/hello-dock
# a37f723dad3ae793ce40f97eb6bb236761baa92d72a2c27c24fc7fda0756657d

docker network inspect --format=<span class="hljs-string">'{{range .Containers}}{{.Name}}{{end}}'</span> bridge
# hello-dock
</code></pre><p>Containers attached to the default bridge network can communicate with each others using IP addresses which I have already discouraged in the previous sub-section.</p>
<p>A user-defined bridge, however, has some extra features over the default one. According to the official <a target="_blank" href="https://docs.docker.com/network/bridge/#differences-between-user-defined-bridges-and-the-default-bridge">docs</a> on this topic, some notable extra features are as follows:</p>
<ul>
<li><strong>User-defined bridges provide automatic DNS resolution between containers:</strong> This means containers attached to the same network can communicate with each others using the container name. So if you have two containers named <code>notes-api</code> and <code>notes-db</code> the API container will be able to connect to the database container using the <code>notes-db</code> name.</li>
<li><strong>User-defined bridges provide better isolation:</strong> All containers are attached to the default bridge network by default which can cause conflicts among them. Attaching containers to a user-defined bridge can ensure better isolation.</li>
<li><strong>Containers can be attached and detached from user-defined networks on the fly:</strong> During a container’s lifetime, you can connect or disconnect it from user-defined networks on the fly. To remove a container from the default bridge network, you need to stop the container and recreate it with different network options.</li>
</ul>
<p>Now that you've learned quite a lot about a user-defined network, it's time to create one for yourself. A network can be created using the <code>network create</code> command. The generic syntax for the command is as follows:</p>
<pre><code>docker network create &lt;network name&gt;
</code></pre><p>To create a network with the name <code>skynet</code> execute the following command:</p>
<pre><code>docker network create skynet

# <span class="hljs-number">7</span>bd5f351aa892ac6ec15fed8619fc3bbb95a7dcdd58980c28304627c8f7eb070

docker network ls

# NETWORK ID     NAME     DRIVER    SCOPE
# be0cab667c4b   bridge   bridge    local
# <span class="hljs-number">124</span>dccee067f   host     host      local
# <span class="hljs-number">506e3822</span>bf1f   none     <span class="hljs-literal">null</span>      local
# <span class="hljs-number">7</span>bd5f351aa89   skynet   bridge    local
</code></pre><p>As you can see a new network has been created with the given name. No container is currently attached to this network. In the next sub-section, you'll learn about attaching containers to a network.</p>
<h3 id="heading-how-to-attach-a-container-to-a-network-in-docker">How to Attach a Container to a Network in Docker</h3>
<p>There are mostly two ways of attaching a container to a network. First, you can use the network connect command to attach a container to a network. The generic syntax for the command is as follows:</p>
<pre><code>docker network connect &lt;network identifier&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">container</span> <span class="hljs-attr">identifier</span>&gt;</span></span>
</code></pre><p>To connect the <code>hello-dock</code> container to the <code>skynet</code> network, you can execute the following command:</p>
<pre><code>docker network connect skynet hello-dock

docker network inspect --format=<span class="hljs-string">'{{range .Containers}} {{.Name}} {{end}}'</span> skynet

#  hello-dock

docker network inspect --format=<span class="hljs-string">'{{range .Containers}} {{.Name}} {{end}}'</span> bridge

#  hello-dock
</code></pre><p>As you can see from the outputs of the two <code>network inspect</code> commands, the <code>hello-dock</code> container is now attached to both the <code>skynet</code> and the default <code>bridge</code> network.</p>
<p>The second way of attaching a container to a network is by using the <code>--network</code> option for the <code>container run</code> or <code>container create</code> commands. The generic syntax for the option is as follows:</p>
<pre><code>--network &lt;network identifier&gt;
</code></pre><p>To run another <code>hello-dock</code> container attached to the same network, you can execute the following command:</p>
<pre><code>docker container run --network skynet --rm --name alpine-box -it alpine sh

# lands you into alpine linux shell

/ # ping hello-dock

# PING hello-dock (<span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>): <span class="hljs-number">56</span> data bytes
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">0</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.191</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">1</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.103</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">2</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.139</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">3</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.142</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">4</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.146</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">5</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.095</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">6</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.181</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">7</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.138</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">8</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.158</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">9</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.137</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">10</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.145</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">11</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.138</span> ms
# <span class="hljs-number">64</span> bytes <span class="hljs-keyword">from</span> <span class="hljs-number">172.18</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>: seq=<span class="hljs-number">12</span> ttl=<span class="hljs-number">64</span> time=<span class="hljs-number">0.085</span> ms

--- hello-dock ping statistics ---
<span class="hljs-number">13</span> packets transmitted, <span class="hljs-number">13</span> packets received, <span class="hljs-number">0</span>% packet loss
round-trip min/avg/max = <span class="hljs-number">0.085</span>/<span class="hljs-number">0.138</span>/<span class="hljs-number">0.191</span> ms
</code></pre><p>As you can see, running <code>ping hello-dock</code> from inside the <code>alpine-box</code> container works because both of the containers are under the same user-defined bridge network and automatic DNS resolution is working.</p>
<p>Keep in mind, though, that in order for the automatic DNS resolution to work you must assign custom names to the containers. Using the randomly generated name will not work.</p>
<h3 id="heading-how-to-detach-containers-from-a-network-in-docker">How to Detach Containers from a Network in Docker</h3>
<p>In the previous sub-section you learned about attaching containers to a network. In this sub-section, you'll learn about how to detach them. </p>
<p>You can use the <code>network disconnect</code> command for this task. The generic syntax for the command is as follows:</p>
<pre><code>docker network disconnect &lt;network identifier&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">container</span> <span class="hljs-attr">identifier</span>&gt;</span></span>
</code></pre><p>To detach the <code>hello-dock</code> container from the <code>skynet</code> network, you can execute the following command:</p>
<pre><code>docker network disconnect skynet hello-dock
</code></pre><p>Just like the <code>network connect</code> command, the <code>network disconnect</code> command doesn't give any output.</p>
<h3 id="heading-how-to-get-rid-of-networks-in-docker">How to Get Rid of Networks in Docker</h3>
<p>Just like the other logical objects in Docker, networks can be removed using the <code>network rm</code> command. The generic syntax for the command is as follows:</p>
<pre><code>docker network rm &lt;network identifier&gt;
</code></pre><p>To remove the <code>skynet</code> network from your system, you can execute the following command:</p>
<pre><code>docker network rm skynet
</code></pre><p>You can also use the <code>network prune</code> command to remove any unused networks from your system. The command also has the <code>-f</code> or <code>--force</code> and <code>-a</code> or <code>--all</code> options.</p>
<h2 id="heading-how-to-containerize-a-multi-container-javascript-application">How to Containerize a Multi-Container JavaScript Application</h2>
<p>Now that you've learned enough about networks in Docker, in this section you'll learn to containerize a full-fledged multi-container project. The project you'll be working with is a simple <code>notes-api</code> powered by Express.js and PostgreSQL.</p>
<p>In this project there are two containers in total that you'll have to connect using a network. Apart from this, you'll also learn about concepts like environment variables and named volumes. So without further ado, let's jump right in.</p>
<h3 id="heading-how-to-run-the-database-server">How to Run the Database Server</h3>
<p>The database server in this project is a simple PostgreSQL server and uses the official <a target="_blank" href="https://hub.docker.com/_/postgres">postgres</a> image. </p>
<p>According to the official docs, in order to run a container with this image, you must provide the <code>POSTGRES_PASSWORD</code> environment variable. Apart from this one, I'll also provide a name for the default database using the <code>POSTGRES_DB</code> environment variable. PostgreSQL by default listens on port <code>5432</code>, so you need to publish that as well.</p>
<p>To run the database server you can execute the following command:</p>
<pre><code>docker container run \
    --detach \
    --name=notes-db \
    --env POSTGRES_DB=notesdb \
    --env POSTGRES_PASSWORD=secret \
    --network=notes-api-network \
    <span class="hljs-attr">postgres</span>:<span class="hljs-number">12</span>

# a7b287d34d96c8e81a63949c57b83d7c1d71b5660c87f5172f074bd1606196dc

docker container ls

# CONTAINER ID   IMAGE         COMMAND                  CREATED              STATUS              PORTS      NAMES
# a7b287d34d96   postgres:<span class="hljs-number">12</span>   <span class="hljs-string">"docker-entrypoint.s…"</span>   About a minute ago   Up About a minute   <span class="hljs-number">5432</span>/tcp   notes-db
</code></pre><p>The <code>--env</code> option for the <code>container run</code> and <code>container create</code> commands can be used for providing environment variables to a container. As you can see, the database container has been created successfully and is running now.</p>
<p>Although the container is running, there is a small problem. Databases like PostgreSQL, MongoDB, and MySQL persist their data in a directory. PostgreSQL uses the <code>/var/lib/postgresql/data</code> directory inside the container to persist data. </p>
<p>Now what if the container gets destroyed for some reason? You'll lose all your data. To solve this problem, a named volume can be used.</p>
<h3 id="heading-how-to-work-with-named-volumes-in-docker">How to Work with Named Volumes in Docker</h3>
<p>Previously you've worked with bind mounts and anonymous volumes. A named volume is very similar to an anonymous volume except that you can refer to a named volume using its name. </p>
<p>Volumes are also logical objects in Docker and can be manipulated using the command-line. The <code>volume create</code> command can be used for creating a named volume.</p>
<p>The generic syntax for the command is as follows:</p>
<pre><code>docker volume create &lt;volume name&gt;
</code></pre><p>To create a volume named <code>notes-db-data</code> you can execute the following command:</p>
<pre><code>docker volume create notes-db-data

# notes-db-data

docker volume ls

# DRIVER    VOLUME NAME
# local     notes-db-data
</code></pre><p>This volume can now be mounted to <code>/var/lib/postgresql/data</code> inside the <code>notes-db</code> container. To do so, stop and remove the <code>notes-db</code> container:</p>
<pre><code>docker container stop notes-db

# notes-db

docker container rm notes-db

# notes-db
</code></pre><p>Now run a new container and assign the volume using the <code>--volume</code> or <code>-v</code> option.</p>
<pre><code>docker container run \
    --detach \
    --volume notes-db-data:<span class="hljs-regexp">/var/</span>lib/postgresql/data \
    --name=notes-db \
    --env POSTGRES_DB=notesdb \
    --env POSTGRES_PASSWORD=secret \
    --network=notes-api-network \
    <span class="hljs-attr">postgres</span>:<span class="hljs-number">12</span>

# <span class="hljs-number">37755e86</span>d62794ed3e67c19d0cd1eba431e26ab56099b92a3456908c1d346791
</code></pre><p>Now inspect the <code>notes-db</code> container to make sure that the mounting was successful:</p>
<pre><code>docker container inspect --format=<span class="hljs-string">'{{range .Mounts}} {{ .Name }} {{end}}'</span> notes-db

#  notes-db-data
</code></pre><p>Now the data will safely be stored inside the <code>notes-db-data</code> volume and can be reused in the future. A bind mount can also be used instead of a named volume here, but I prefer a named volume in such scenarios.</p>
<h3 id="heading-how-to-access-logs-from-a-container-in-docker">How to Access Logs from a Container in Docker</h3>
<p>In order to see the logs from a container, you can use the <code>container logs</code> command. The generic syntax for the command is as follows:</p>
<pre><code>docker container logs &lt;container identifier&gt;
</code></pre><p>To access the logs from the <code>notes-db</code> container, you can execute the following command:</p>
<pre><code>docker container logs notes-db

# The files belonging to <span class="hljs-built_in">this</span> database system will be owned by user <span class="hljs-string">"postgres"</span>.
# This user must also own the server process.

# The database cluster will be initialized <span class="hljs-keyword">with</span> locale <span class="hljs-string">"en_US.utf8"</span>.
# The <span class="hljs-keyword">default</span> database encoding has accordingly been set to <span class="hljs-string">"UTF8"</span>.
# The <span class="hljs-keyword">default</span> text search configuration will be set to <span class="hljs-string">"english"</span>.
#
# Data page checksums are disabled.
#
# fixing permissions on existing directory /<span class="hljs-keyword">var</span>/lib/postgresql/data ... ok
# creating subdirectories ... ok
# selecting dynamic shared memory implementation ... posix
# selecting <span class="hljs-keyword">default</span> max_connections ... <span class="hljs-number">100</span>
# selecting <span class="hljs-keyword">default</span> shared_buffers ... <span class="hljs-number">128</span>MB
# selecting <span class="hljs-keyword">default</span> time zone ... Etc/UTC
# creating configuration files ... ok
# running bootstrap script ... ok
# performing post-bootstrap initialization ... ok
# syncing data to disk ... ok
#
#
# Success. You can now start the database server using:
#
#     pg_ctl -D /<span class="hljs-keyword">var</span>/lib/postgresql/data -l logfile start
#
# initdb: warning: enabling <span class="hljs-string">"trust"</span> authentication <span class="hljs-keyword">for</span> local connections
# You can change <span class="hljs-built_in">this</span> by editing pg_hba.conf or using the option -A, or
# --auth-local and --auth-host, the next time you run initdb.
# waiting <span class="hljs-keyword">for</span> server to start...<span class="hljs-number">.2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21.613</span> UTC [<span class="hljs-number">47</span>] LOG:  starting PostgreSQL <span class="hljs-number">12.5</span> (Debian <span class="hljs-number">12.5</span><span class="hljs-number">-1.</span>pgdg100+<span class="hljs-number">1</span>) on x86_64-pc-linux-gnu, compiled by gcc (Debian <span class="hljs-number">8.3</span><span class="hljs-number">.0</span><span class="hljs-number">-6</span>) <span class="hljs-number">8.3</span><span class="hljs-number">.0</span>, <span class="hljs-number">64</span>-bit
# <span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21.621</span> UTC [<span class="hljs-number">47</span>] LOG:  listening on Unix socket <span class="hljs-string">"/var/run/postgresql/.s.PGSQL.5432"</span>
# <span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21.675</span> UTC [<span class="hljs-number">48</span>] LOG:  database system was shut down at <span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21</span> UTC
# <span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-25</span> <span class="hljs-number">13</span>:<span class="hljs-number">39</span>:<span class="hljs-number">21.685</span> UTC [<span class="hljs-number">47</span>] LOG:  database system is ready to accept connections
#  done
# server started
# CREATE DATABASE
#
#
# /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d<span class="hljs-comment">/*
#
# 2021-01-25 13:39:22.008 UTC [47] LOG:  received fast shutdown request
# waiting for server to shut down....2021-01-25 13:39:22.015 UTC [47] LOG:  aborting any active transactions
# 2021-01-25 13:39:22.017 UTC [47] LOG:  background worker "logical replication launcher" (PID 54) exited with exit code 1
# 2021-01-25 13:39:22.017 UTC [49] LOG:  shutting down
# 2021-01-25 13:39:22.056 UTC [47] LOG:  database system is shut down
#  done
# server stopped
#
# PostgreSQL init process complete; ready for start up.
#
# 2021-01-25 13:39:22.135 UTC [1] LOG:  starting PostgreSQL 12.5 (Debian 12.5-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
# 2021-01-25 13:39:22.136 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
# 2021-01-25 13:39:22.136 UTC [1] LOG:  listening on IPv6 address "::", port 5432
# 2021-01-25 13:39:22.147 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
# 2021-01-25 13:39:22.177 UTC [75] LOG:  database system was shut down at 2021-01-25 13:39:22 UTC
# 2021-01-25 13:39:22.190 UTC [1] LOG:  database system is ready to accept connections</span>
</code></pre><p>Evident by the text in line 57, the database is up and ready to accept connections from the outside. There is also the <code>--follow</code> or <code>-f</code> option for the command which lets you attach the console to the logs output and get a continuous stream of text.</p>
<h3 id="heading-how-to-create-a-network-and-attaching-the-database-server-in-docker">How to Create a Network and Attaching the Database Server in Docker</h3>
<p>As you've learned in the previous section, the containers have to be attached to a user-defined bridge network in order to communicate with each other using container names. To do so, create a network named <code>notes-api-network</code> in your system:</p>
<pre><code>docker network create notes-api-network
</code></pre><p>Now attach the <code>notes-db</code> container to this network by executing the following command:</p>
<pre><code>docker network connect notes-api-network notes-db
</code></pre><h3 id="heading-how-to-write-the-dockerfile">How to Write the Dockerfile</h3>
<p>Go to the directory where you've cloned the project code. Inside there, go inside the <code>notes-api/api</code> directory, and create a new <code>Dockerfile</code>. Put the following code in the file:</p>
<pre><code># stage one
FROM node:lts-alpine <span class="hljs-keyword">as</span> builder

# install dependencies <span class="hljs-keyword">for</span> node-gyp
RUN apk add --no-cache python make g++

WORKDIR /app

COPY ./package.json .
RUN npm install --only=prod

# stage two
FROM node:lts-alpine

EXPOSE <span class="hljs-number">3000</span>
ENV NODE_ENV=production

USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app

COPY . .
COPY --<span class="hljs-keyword">from</span>=builder /app/node_modules  /home/node/app/node_modules

CMD [ <span class="hljs-string">"node"</span>, <span class="hljs-string">"bin/www"</span> ]
</code></pre><p>This is a multi-staged build. The first stage is used for building and installing the dependencies using <code>node-gyp</code> and the second stage is for running the application. I'll go through the steps briefly:</p>
<ul>
<li>Stage 1 uses <code>node:lts-alpine</code> as its base and uses <code>builder</code> as the stage name.</li>
<li>On line 5, we install <code>python</code>, <code>make</code>, and <code>g++</code>. The <code>node-gyp</code> tool requires these three packages to run.</li>
<li>On line 7, we set <code>/app</code> directory as the <code>WORKDIR</code> .</li>
<li>On line 9 and 10, we copy the <code>package.json</code> file to the <code>WORKDIR</code> and install all the dependencies.</li>
<li>Stage 2 also uses <code>node-lts:alpine</code> as the base.</li>
<li>On line 16, we set the <code>NODE_ENV</code> environment variable to <code>production</code>. This is important for the API to run properly.</li>
<li>From line 18 to line 20, we set the default user to <code>node</code>, create the <code>/home/node/app</code> directory, and set that as the <code>WORKDIR</code>.</li>
<li>On line 22, we copy all the project files and on line 23 we copy the <code>node_modules</code> directory from the <code>builder</code> stage. This directory contains all the built dependencies necessary for running the application.</li>
<li>On line 25, we set the default command.</li>
</ul>
<p>To build an image from this <code>Dockerfile</code>, you can execute the following command:</p>
<pre><code>docker image build --tag notes-api .

# Sending build context to Docker daemon  <span class="hljs-number">37.38</span>kB
# Step <span class="hljs-number">1</span>/<span class="hljs-number">14</span> : FROM node:lts-alpine <span class="hljs-keyword">as</span> builder
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">2</span>/<span class="hljs-number">14</span> : RUN apk add --no-cache python make g++
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">5</span>f20a0ecc04b
# fetch http:<span class="hljs-comment">//dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz</span>
# fetch http:<span class="hljs-comment">//dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz</span>
# (<span class="hljs-number">1</span>/<span class="hljs-number">21</span>) Installing binutils (<span class="hljs-number">2.33</span><span class="hljs-number">.1</span>-r0)
# (<span class="hljs-number">2</span>/<span class="hljs-number">21</span>) Installing gmp (<span class="hljs-number">6.1</span><span class="hljs-number">.2</span>-r1)
# (<span class="hljs-number">3</span>/<span class="hljs-number">21</span>) Installing isl (<span class="hljs-number">0.18</span>-r0)
# (<span class="hljs-number">4</span>/<span class="hljs-number">21</span>) Installing libgomp (<span class="hljs-number">9.3</span><span class="hljs-number">.0</span>-r0)
# (<span class="hljs-number">5</span>/<span class="hljs-number">21</span>) Installing libatomic (<span class="hljs-number">9.3</span><span class="hljs-number">.0</span>-r0)
# (<span class="hljs-number">6</span>/<span class="hljs-number">21</span>) Installing mpfr4 (<span class="hljs-number">4.0</span><span class="hljs-number">.2</span>-r1)
# (<span class="hljs-number">7</span>/<span class="hljs-number">21</span>) Installing mpc1 (<span class="hljs-number">1.1</span><span class="hljs-number">.0</span>-r1)
# (<span class="hljs-number">8</span>/<span class="hljs-number">21</span>) Installing gcc (<span class="hljs-number">9.3</span><span class="hljs-number">.0</span>-r0)
# (<span class="hljs-number">9</span>/<span class="hljs-number">21</span>) Installing musl-dev (<span class="hljs-number">1.1</span><span class="hljs-number">.24</span>-r3)
# (<span class="hljs-number">10</span>/<span class="hljs-number">21</span>) Installing libc-dev (<span class="hljs-number">0.7</span><span class="hljs-number">.2</span>-r0)
# (<span class="hljs-number">11</span>/<span class="hljs-number">21</span>) Installing g++ (<span class="hljs-number">9.3</span><span class="hljs-number">.0</span>-r0)
# (<span class="hljs-number">12</span>/<span class="hljs-number">21</span>) Installing make (<span class="hljs-number">4.2</span><span class="hljs-number">.1</span>-r2)
# (<span class="hljs-number">13</span>/<span class="hljs-number">21</span>) Installing libbz2 (<span class="hljs-number">1.0</span><span class="hljs-number">.8</span>-r1)
# (<span class="hljs-number">14</span>/<span class="hljs-number">21</span>) Installing expat (<span class="hljs-number">2.2</span><span class="hljs-number">.9</span>-r1)
# (<span class="hljs-number">15</span>/<span class="hljs-number">21</span>) Installing libffi (<span class="hljs-number">3.2</span><span class="hljs-number">.1</span>-r6)
# (<span class="hljs-number">16</span>/<span class="hljs-number">21</span>) Installing gdbm (<span class="hljs-number">1.13</span>-r1)
# (<span class="hljs-number">17</span>/<span class="hljs-number">21</span>) Installing ncurses-terminfo-base (<span class="hljs-number">6.1</span>_p20200118-r4)
# (<span class="hljs-number">18</span>/<span class="hljs-number">21</span>) Installing ncurses-libs (<span class="hljs-number">6.1</span>_p20200118-r4)
# (<span class="hljs-number">19</span>/<span class="hljs-number">21</span>) Installing readline (<span class="hljs-number">8.0</span><span class="hljs-number">.1</span>-r0)
# (<span class="hljs-number">20</span>/<span class="hljs-number">21</span>) Installing sqlite-libs (<span class="hljs-number">3.30</span><span class="hljs-number">.1</span>-r2)
# (<span class="hljs-number">21</span>/<span class="hljs-number">21</span>) Installing python2 (<span class="hljs-number">2.7</span><span class="hljs-number">.18</span>-r0)
# Executing busybox<span class="hljs-number">-1.31</span><span class="hljs-number">.1</span>-r9.trigger
# OK: <span class="hljs-number">212</span> MiB <span class="hljs-keyword">in</span> <span class="hljs-number">37</span> packages
# Removing intermediate container <span class="hljs-number">5</span>f20a0ecc04b
#  ---&gt; <span class="hljs-number">637</span>ca797d709
# Step <span class="hljs-number">3</span>/<span class="hljs-number">14</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">846361</span>b57599
# Removing intermediate container <span class="hljs-number">846361</span>b57599
#  ---&gt; <span class="hljs-number">3</span>d58a482896e
# Step <span class="hljs-number">4</span>/<span class="hljs-number">14</span> : COPY ./package.json .
#  ---&gt; <span class="hljs-number">11</span>b387794039
# Step <span class="hljs-number">5</span>/<span class="hljs-number">14</span> : RUN npm install --only=prod
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">2e27</span>e33f935d
#  added <span class="hljs-number">269</span> packages <span class="hljs-keyword">from</span> <span class="hljs-number">220</span> contributors and audited <span class="hljs-number">1137</span> packages <span class="hljs-keyword">in</span> <span class="hljs-number">140.322</span>s
#
# <span class="hljs-number">4</span> packages are looking <span class="hljs-keyword">for</span> funding
#   run <span class="hljs-string">`npm fund`</span> <span class="hljs-keyword">for</span> details
#
# found <span class="hljs-number">0</span> vulnerabilities
#
# Removing intermediate container <span class="hljs-number">2e27</span>e33f935d
#  ---&gt; eb7cb2cb0b20
# Step <span class="hljs-number">6</span>/<span class="hljs-number">14</span> : FROM node:lts-alpine
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">7</span>/<span class="hljs-number">14</span> : EXPOSE <span class="hljs-number">3000</span>
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">4</span>ea24f871747
# Removing intermediate container <span class="hljs-number">4</span>ea24f871747
#  ---&gt; <span class="hljs-number">1</span>f0206f2f050
# Step <span class="hljs-number">8</span>/<span class="hljs-number">14</span> : ENV NODE_ENV=production
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">5</span>d40d6ac3b7e
# Removing intermediate container <span class="hljs-number">5</span>d40d6ac3b7e
#  ---&gt; <span class="hljs-number">31</span>f62da17929
# Step <span class="hljs-number">9</span>/<span class="hljs-number">14</span> : USER node
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">0963e1</span>fb19a0
# Removing intermediate container <span class="hljs-number">0963e1</span>fb19a0
#  ---&gt; <span class="hljs-number">0</span>f4045152b1c
# Step <span class="hljs-number">10</span>/<span class="hljs-number">14</span> : RUN mkdir -p /home/node/app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>ac591b3adbd
# Removing intermediate container <span class="hljs-number">0</span>ac591b3adbd
#  ---&gt; <span class="hljs-number">5908373</span>dfc75
# Step <span class="hljs-number">11</span>/<span class="hljs-number">14</span> : WORKDIR /home/node/app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">55253</span>b62ff57
# Removing intermediate container <span class="hljs-number">55253</span>b62ff57
#  ---&gt; <span class="hljs-number">2883</span>cdb7c77a
# Step <span class="hljs-number">12</span>/<span class="hljs-number">14</span> : COPY . .
#  ---&gt; <span class="hljs-number">8e60893</span>a7142
# Step <span class="hljs-number">13</span>/<span class="hljs-number">14</span> : COPY --<span class="hljs-keyword">from</span>=builder /app/node_modules  /home/node/app/node_modules
#  ---&gt; <span class="hljs-number">27</span>a85faa4342
# Step <span class="hljs-number">14</span>/<span class="hljs-number">14</span> : CMD [ <span class="hljs-string">"node"</span>, <span class="hljs-string">"bin/www"</span> ]
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">349</span>c8ca6dd3e
# Removing intermediate container <span class="hljs-number">349</span>c8ca6dd3e
#  ---&gt; <span class="hljs-number">9</span>ea100571585
# Successfully built <span class="hljs-number">9</span>ea100571585
# Successfully tagged notes-api:latest
</code></pre><p>Before you run a container using this image, make sure the database container is running, and is attached to the <code>notes-api-network</code>.</p>
<pre><code>docker container inspect notes-db

# [
#     {
#         ...
#         <span class="hljs-string">"State"</span>: {
#             <span class="hljs-string">"Status"</span>: <span class="hljs-string">"running"</span>,
#             <span class="hljs-string">"Running"</span>: <span class="hljs-literal">true</span>,
#             <span class="hljs-string">"Paused"</span>: <span class="hljs-literal">false</span>,
#             <span class="hljs-string">"Restarting"</span>: <span class="hljs-literal">false</span>,
#             <span class="hljs-string">"OOMKilled"</span>: <span class="hljs-literal">false</span>,
#             <span class="hljs-string">"Dead"</span>: <span class="hljs-literal">false</span>,
#             <span class="hljs-string">"Pid"</span>: <span class="hljs-number">11521</span>,
#             <span class="hljs-string">"ExitCode"</span>: <span class="hljs-number">0</span>,
#             <span class="hljs-string">"Error"</span>: <span class="hljs-string">""</span>,
#             <span class="hljs-string">"StartedAt"</span>: <span class="hljs-string">"2021-01-26T06:55:44.928510218Z"</span>,
#             <span class="hljs-string">"FinishedAt"</span>: <span class="hljs-string">"2021-01-25T14:19:31.316854657Z"</span>
#         },
#         ...
#         <span class="hljs-string">"Mounts"</span>: [
#             {
#                 <span class="hljs-string">"Type"</span>: <span class="hljs-string">"volume"</span>,
#                 <span class="hljs-string">"Name"</span>: <span class="hljs-string">"notes-db-data"</span>,
#                 <span class="hljs-string">"Source"</span>: <span class="hljs-string">"/var/lib/docker/volumes/notes-db-data/_data"</span>,
#                 <span class="hljs-string">"Destination"</span>: <span class="hljs-string">"/var/lib/postgresql/data"</span>,
#                 <span class="hljs-string">"Driver"</span>: <span class="hljs-string">"local"</span>,
#                 <span class="hljs-string">"Mode"</span>: <span class="hljs-string">"z"</span>,
#                 <span class="hljs-string">"RW"</span>: <span class="hljs-literal">true</span>,
#                 <span class="hljs-string">"Propagation"</span>: <span class="hljs-string">""</span>
#             }
#         ],
#         ...
#         <span class="hljs-string">"NetworkSettings"</span>: {
#             ...
#             <span class="hljs-string">"Networks"</span>: {
#                 <span class="hljs-string">"bridge"</span>: {
#                     <span class="hljs-string">"IPAMConfig"</span>: <span class="hljs-literal">null</span>,
#                     <span class="hljs-string">"Links"</span>: <span class="hljs-literal">null</span>,
#                     <span class="hljs-string">"Aliases"</span>: <span class="hljs-literal">null</span>,
#                     <span class="hljs-string">"NetworkID"</span>: <span class="hljs-string">"e4c7ce50a5a2a49672155ff498597db336ecc2e3bbb6ee8baeebcf9fcfa0e1ab"</span>,
#                     <span class="hljs-string">"EndpointID"</span>: <span class="hljs-string">"2a2587f8285fa020878dd38bdc630cdfca0d769f76fc143d1b554237ce907371"</span>,
#                     <span class="hljs-string">"Gateway"</span>: <span class="hljs-string">"172.17.0.1"</span>,
#                     <span class="hljs-string">"IPAddress"</span>: <span class="hljs-string">"172.17.0.2"</span>,
#                     <span class="hljs-string">"IPPrefixLen"</span>: <span class="hljs-number">16</span>,
#                     <span class="hljs-string">"IPv6Gateway"</span>: <span class="hljs-string">""</span>,
#                     <span class="hljs-string">"GlobalIPv6Address"</span>: <span class="hljs-string">""</span>,
#                     <span class="hljs-string">"GlobalIPv6PrefixLen"</span>: <span class="hljs-number">0</span>,
#                     <span class="hljs-string">"MacAddress"</span>: <span class="hljs-string">"02:42:ac:11:00:02"</span>,
#                     <span class="hljs-string">"DriverOpts"</span>: <span class="hljs-literal">null</span>
#                 },
#                 <span class="hljs-string">"notes-api-network"</span>: {
#                     <span class="hljs-string">"IPAMConfig"</span>: {},
#                     <span class="hljs-string">"Links"</span>: <span class="hljs-literal">null</span>,
#                     <span class="hljs-string">"Aliases"</span>: [
#                         <span class="hljs-string">"37755e86d627"</span>
#                     ],
#                     <span class="hljs-string">"NetworkID"</span>: <span class="hljs-string">"06579ad9f93d59fc3866ac628ed258dfac2ed7bc1a9cd6fe6e67220b15d203ea"</span>,
#                     <span class="hljs-string">"EndpointID"</span>: <span class="hljs-string">"5b8f8718ec9a5ec53e7a13cce3cb540fdf3556fb34242362a8da4cc08d37223c"</span>,
#                     <span class="hljs-string">"Gateway"</span>: <span class="hljs-string">"172.18.0.1"</span>,
#                     <span class="hljs-string">"IPAddress"</span>: <span class="hljs-string">"172.18.0.2"</span>,
#                     <span class="hljs-string">"IPPrefixLen"</span>: <span class="hljs-number">16</span>,
#                     <span class="hljs-string">"IPv6Gateway"</span>: <span class="hljs-string">""</span>,
#                     <span class="hljs-string">"GlobalIPv6Address"</span>: <span class="hljs-string">""</span>,
#                     <span class="hljs-string">"GlobalIPv6PrefixLen"</span>: <span class="hljs-number">0</span>,
#                     <span class="hljs-string">"MacAddress"</span>: <span class="hljs-string">"02:42:ac:12:00:02"</span>,
#                     <span class="hljs-string">"DriverOpts"</span>: {}
#                 }
#             }
#         }
#     }
# ]
</code></pre><p>I've shortened the output for easy viewing here. On my system, the <code>notes-db</code> container is running, uses the <code>notes-db-data</code> volume, and is attached to the <code>notes-api-network</code> bridge.</p>
<p>Once you're assured that everything is in place, you can run a new container by executing the following command:</p>
<pre><code>docker container run \
    --detach \
    --name=notes-api \
    --env DB_HOST=notes-db \
    --env DB_DATABASE=notesdb \
    --env DB_PASSWORD=secret \
    --publish=<span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> \
    --network=notes-api-network \
    notes-api

# f9ece420872de99a060b954e3c236cbb1e23d468feffa7fed1e06985d99fb919
</code></pre><p>You should be able to understand this long command by yourself, so I'll go through the environment variables briefly. </p>
<p>The <code>notes-api</code> application requires three environment variables to be set. They are as follows:</p>
<ul>
<li><code>DB_HOST</code> - This is the host of the database server. Given that both the database server and the API are attached to the same user-defined bridge network, the database server can be refereed to using its container name which is <code>notes-db</code> in this case.</li>
<li><code>DB_DATABASE</code> - The database that this API will use. On <a target="_blank" href="https://www.freecodecamp.org/news/@fhsinchy/s/the-docker-handbook/~/drafts/-MS2MtB5zjVVjK3Ujaz4/containerizing-a-multi-container-javascript-application#running-the-database-server">Running the Database Server</a> we set the default database name to <code>notesdb</code> using the <code>POSTGRES_DB</code> environment variable. We'll use that here.</li>
<li><code>DB_PASSWORD</code> - Password for connecting to the database. This was also set on <a target="_blank" href="https://www.freecodecamp.org/news/@fhsinchy/s/the-docker-handbook/~/drafts/-MS2MtB5zjVVjK3Ujaz4/containerizing-a-multi-container-javascript-application#running-the-database-server">Running the Database Server</a> sub-section using the <code>POSTGRES_PASSWORD</code> environment variable.</li>
</ul>
<p>To check if the container is running properly or not, you can use the <code>container ls</code> command:</p>
<pre><code>docker container ls

# CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                    NAMES
# f9ece420872d   notes-api     <span class="hljs-string">"docker-entrypoint.s…"</span>   <span class="hljs-number">12</span> minutes ago   Up <span class="hljs-number">12</span> minutes   <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">3000</span>-&gt;<span class="hljs-number">3000</span>/tcp   notes-api
# <span class="hljs-number">37755e86</span>d627   postgres:<span class="hljs-number">12</span>   <span class="hljs-string">"docker-entrypoint.s…"</span>   <span class="hljs-number">17</span> hours ago     Up <span class="hljs-number">14</span> minutes   <span class="hljs-number">5432</span>/tcp                 notes-db
</code></pre><p>The container is running now. You can visit <code>http://127.0.0.1:3000/</code> to see the API in action.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/bonjour-mon-ami.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The API has five routes in total that you can see inside the <code>/notes-api/api/api/routes/notes.js</code> file.</p>
<p>Although the container is running, there is one last thing that you'll have to do before you can start using it. You'll have to run the database migration necessary for setting up the database tables, and you can do that by executing <code>npm run db:migrate</code> command inside the container.</p>
<h3 id="heading-how-to-execute-commands-in-a-running-container">How to Execute Commands in a Running Container</h3>
<p>You've already learned about executing commands in a stopped container. Another scenario is executing a command inside a running container.</p>
<p>For this, you'll have to use the <code>exec</code> command to execute a custom command inside a running container.</p>
<p>The generic syntax for the <code>exec</code> command is as follows:</p>
<pre><code>docker container exec &lt;container identifier&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">command</span>&gt;</span></span>
</code></pre><p>To execute <code>npm run db:migrate</code> inside the <code>notes-api</code> container, you can execute the following command:</p>
<pre><code>docker container exec notes-api npm run db:migrate

# &gt; notes-api@ db:migrate /home/node/app
# &gt; knex migrate:latest
#
# Using environment: production
# Batch <span class="hljs-number">1</span> run: <span class="hljs-number">1</span> migrations
</code></pre><p>In cases where you want to run an interactive command inside a running container, you'll have to use the <code>-it</code> flag. As an example, if you want to access the shell running inside the <code>notes-api</code> container, you can execute following the command:</p>
<pre><code>docker container exec -it notes-api sh

# / # uname -a
# Linux b5b1367d6b31 <span class="hljs-number">5.10</span><span class="hljs-number">.9</span><span class="hljs-number">-201.</span>fc33.x86_64 #<span class="hljs-number">1</span> SMP Wed Jan <span class="hljs-number">20</span> <span class="hljs-number">16</span>:<span class="hljs-number">56</span>:<span class="hljs-number">23</span> UTC <span class="hljs-number">2021</span> x86_64 Linux
</code></pre><h3 id="heading-how-to-write-management-scripts-in-docker">How to Write Management Scripts in Docker</h3>
<p>Managing a multi-container project along with the network and volumes and stuff means writing a lot of commands. To simplify the process, I usually have help from simple <a target="_blank" href="https://opensource.com/article/17/1/getting-started-shell-scripting">shell scripts</a> and a <a target="_blank" href="https://opensource.com/article/18/8/what-how-makefile">Makefile</a>. </p>
<p>You'll find four shell scripts in the <code>notes-api</code> directory. They are as follows:</p>
<ul>
<li><code>boot.sh</code> - Used for starting the containers if they already exist.</li>
<li><code>build.sh</code> - Creates and runs the containers. It also creates the images, volumes, and networks if necessary.</li>
<li><code>destroy.sh</code> - Removes all containers, volumes and networks associated with this project.</li>
<li><code>stop.sh</code> - Stops all running containers.</li>
</ul>
<p>There is also a <code>Makefile</code> that contains four targets named <code>start</code>, <code>stop</code>, <code>build</code> and <code>destroy</code>, each invoking the previously mentioned shell scripts.</p>
<p>If the container is in a running state in your system, executing <code>make stop</code> should stop all the containers. Executing <code>make destroy</code> should stop the containers and remove everything. Make sure you're running the scripts inside the <code>notes-api</code> directory:</p>
<pre><code>make destroy

# ./shutdown.sh
# stopping api container ---&gt;
# notes-api
# api container stopped ---&gt;

# stopping db container ---&gt;
# notes-db
# db container stopped ---&gt;

# shutdown script finished

# ./destroy.sh
# removing api container ---&gt;
# notes-api
# api container removed ---&gt;

# removing db container ---&gt;
# notes-db
# db container removed ---&gt;

# removing db data volume ---&gt;
# notes-db-data
# db data volume removed ---&gt;

# removing network ---&gt;
# notes-api-network
# network removed ---&gt;

# destroy script finished
</code></pre><p>If you're getting a permission denied error, than execute <code>chmod +x</code> on the scripts:</p>
<pre><code>chmod +x boot.sh build.sh destroy.sh shutdown.sh
</code></pre><p>I'm not going to explain these scripts because they're simple <code>if-else</code> statements along with some Docker commands that you've already seen many times. If you have some understanding of the Linux shell, you should be able to understand the scripts as well.</p>
<h2 id="heading-how-to-compose-projects-using-docker-compose">How to Compose Projects Using Docker-Compose</h2>
<p>In the previous section, you've learned about managing a multi-container project and the difficulties of it. Instead of writing so many commands, there is an easier way to manage multi-container projects, a tool called <a target="_blank" href="https://docs.docker.com/compose/">Docker Compose</a>.</p>
<p>According to the Docker <a target="_blank" href="https://docs.docker.com/compose/">documentation</a> -</p>
<blockquote>
<p>Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.</p>
</blockquote>
<p>Although Compose works in all environments, it's more focused on development and testing. Using Compose on a production environment is not recommended at all.</p>
<h3 id="heading-docker-compose-basics">Docker Compose Basics</h3>
<p>Go the directory where you've cloned the repository that came with this book. Go inside the <code>notes-api/api</code> directory and create a <code>Dockerfile.dev</code> file. Put the following code in it:</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># stage one</span>
<span class="hljs-keyword">FROM</span> node:lts-alpine as builder

<span class="hljs-comment"># install dependencies for node-gyp</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apk add --no-cache python make g++</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> ./package.json .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>

<span class="hljs-comment"># stage two</span>
<span class="hljs-keyword">FROM</span> node:lts-alpine

<span class="hljs-keyword">ENV</span> NODE_ENV=development

<span class="hljs-keyword">USER</span> node
<span class="hljs-keyword">RUN</span><span class="bash"> mkdir -p /home/node/app</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /home/node/app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/node_modules /home/node/app/node_modules</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [ <span class="hljs-string">"./node_modules/.bin/nodemon"</span>, <span class="hljs-string">"--config"</span>, <span class="hljs-string">"nodemon.json"</span>, <span class="hljs-string">"bin/www"</span> ]</span>
</code></pre>
<p>The code is almost identical to the <code>Dockerfile</code> that you worked with in the previous section. The three differences in this file are as follows:</p>
<ul>
<li>On line 10, we run <code>npm install</code> instead of <code>npm run install --only=prod</code> because we want the development dependencies also.</li>
<li>On line 15, we set the <code>NODE_ENV</code> environment variable to <code>development</code> instead of <code>production</code>.</li>
<li>On line 24, we use a tool called <a target="_blank" href="https://nodemon.io/">nodemon</a> to get the hot-reload feature for the API.</li>
</ul>
<p>You already know that this project has two containers:</p>
<ul>
<li><code>notes-db</code> - A database server powered by PostgreSQL.</li>
<li><code>notes-api</code> - A REST API powered by Express.js</li>
</ul>
<p>In the world of Compose, each container that makes up the application is known as a service. The first step in composing a multi-container project is to define these services.</p>
<p>Just like the Docker daemon uses a <code>Dockerfile</code> for building images, Docker Compose uses a <code>docker-compose.yaml</code> file to read service definitions from.</p>
<p>Head to the <code>notes-api</code> directory and create a new <code>docker-compose.yaml</code> file. Put the following code into the newly created file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>

<span class="hljs-attr">services:</span> 
    <span class="hljs-attr">db:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-db-dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">notes-db-dev-data:/var/lib/postgresql/data</span>
        <span class="hljs-attr">environment:</span>
            <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">secret</span>
    <span class="hljs-attr">api:</span>
        <span class="hljs-attr">build:</span>
            <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">notes-api:dev</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-api-dev</span>
        <span class="hljs-attr">environment:</span> 
            <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">db</span> <span class="hljs-comment">## same as the database service name</span>
            <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">DB_PASSWORD:</span> <span class="hljs-string">secret</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./api:/home/node/app</span>
        <span class="hljs-attr">ports:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-number">3000</span><span class="hljs-string">:3000</span>

<span class="hljs-attr">volumes:</span>
    <span class="hljs-attr">notes-db-dev-data:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">notes-db-dev-data</span>
</code></pre>
<p>Every valid <code>docker-compose.yaml</code> file starts by defining the file version. At the time of writing, <code>3.8</code> is the latest version. You can look up the latest version <a target="_blank" href="https://docs.docker.com/compose/compose-file/">here</a>.</p>
<p>Blocks in an YAML file are defined by indentation. I will go through each of the blocks and will explain what they do.</p>
<ul>
<li>The <code>services</code> block holds the definitions for each of the services or containers in the application. <code>db</code> and <code>api</code> are the two services that comprise this project.</li>
<li>The <code>db</code> block defines a new service in the application and holds necessary information to start the container. Every service requires either a pre-built image or a <code>Dockerfile</code> to run a container. For the <code>db</code> service we're using the official PostgreSQL image.</li>
<li>Unlike the <code>db</code> service, a pre-built image for the <code>api</code> service doesn't exist. So we'll use the <code>Dockerfile.dev</code> file.</li>
<li>The <code>volumes</code> block defines any name volume needed by any of the services. At the time it only enlists <code>notes-db-dev-data</code> volume used by the <code>db</code> service.</li>
</ul>
<p>Now that have a high level overview of the <code>docker-compose.yaml</code> file, let's have a closer look at the individual services.</p>
<p>The definition code for the <code>db</code> service is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-db-dev</span>
    <span class="hljs-attr">volumes:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-string">db-data:/var/lib/postgresql/data</span>
    <span class="hljs-attr">environment:</span>
        <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">notesdb</span>
        <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">secret</span>
</code></pre>
<ul>
<li>The <code>image</code> key holds the image repository and tag used for this container. We're using the <code>postgres:12</code> image for running the database container.</li>
<li>The <code>container_name</code> indicates the name of the container. By default containers are named following <code>&lt;project directory name&gt;_&lt;service name&gt;</code> syntax. You can override that using <code>container_name</code>.</li>
<li>The <code>volumes</code> array holds the volume mappings for the service and supports named volumes, anonymous volumes, and bind mounts. The syntax <code>&lt;source&gt;:&lt;destination&gt;</code> is identical to what you've seen before.</li>
<li>The <code>environment</code> map holds the values of the various environment variables needed for the service.</li>
</ul>
<p>Definition code for the <code>api</code> service is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">api:</span>
    <span class="hljs-attr">build:</span>
        <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
        <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">notes-api:dev</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-api-dev</span>
    <span class="hljs-attr">environment:</span> 
        <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">db</span> <span class="hljs-comment">## same as the database service name</span>
        <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
        <span class="hljs-attr">DB_PASSWORD:</span> <span class="hljs-string">secret</span>
    <span class="hljs-attr">volumes:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">./api:/home/node/app</span>
    <span class="hljs-attr">ports:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-number">3000</span><span class="hljs-string">:3000</span>
</code></pre>
<ul>
<li>The <code>api</code> service doesn't come with a pre-built image. Instead it has a build configuration. Under the <code>build</code> block we define the context and the name of the Dockerfile for building an image. You should have an understanding of context and Dockerfile by now so I won't spend time explaining those.</li>
<li>The <code>image</code> key holds the name of the image to be built. If not assigned, the image will be named following the <code>&lt;project directory name&gt;_&lt;service name&gt;</code> syntax.</li>
<li>Inside the <code>environment</code> map, the <code>DB_HOST</code> variable demonstrates a feature of Compose. That is, you can refer to another service in the same application by using its name. So the <code>db</code> here, will be replaced by the IP address of the <code>api</code> service container. The <code>DB_DATABASE</code> and <code>DB_PASSWORD</code> variables have to match up with <code>POSTGRES_DB</code> and <code>POSTGRES_PASSWORD</code> respectively from the <code>db</code> service definition.</li>
<li>In the <code>volumes</code> map, you can see an anonymous volume and a bind mount described. The syntax is identical to what you've seen in previous sections.</li>
<li>The <code>ports</code> map defines any port mapping. The syntax, <code>&lt;host port&gt;:&lt;container port&gt;</code> is identical to the <code>--publish</code> option you used before.</li>
</ul>
<p>Finally, the code for the <code>volumes</code> is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">volumes:</span>
    <span class="hljs-attr">db-data:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">notes-db-dev-data</span>
</code></pre>
<p>Any named volume used in any of the services has to be defined here. If you don't define a name, the volume will be named following the <code>&lt;project directory name&gt;_&lt;volume key&gt;</code> and the key here is <code>db-data</code>. </p>
<p>You can learn about the different options for volume configuration in the official <a target="_blank" href="https://docs.docker.com/compose/compose-file/compose-file-v3/#volumes">docs</a>.</p>
<h3 id="heading-how-to-start-services-in-docker-compose">How to Start Services in Docker Compose</h3>
<p>There are a few ways of starting services defined in a YAML file. The first command that you'll learn about is the <code>up</code> command. The <code>up</code> command builds any missing images, creates containers, and starts them in one go.</p>
<p>Before you execute the command, though, make sure you've opened your terminal in the same directory where the <code>docker-compose.yaml</code> file is. This is very important for every <code>docker-compose</code> command you execute.</p>
<pre><code>docker-compose --file docker-compose.yaml up --detach

# Creating network <span class="hljs-string">"notes-api_default"</span> <span class="hljs-keyword">with</span> the <span class="hljs-keyword">default</span> driver
# Creating volume <span class="hljs-string">"notes-db-dev-data"</span> <span class="hljs-keyword">with</span> <span class="hljs-keyword">default</span> driver
# Building api
# Sending build context to Docker daemon  <span class="hljs-number">37.38</span>kB
#
# Step <span class="hljs-number">1</span>/<span class="hljs-number">13</span> : FROM node:lts-alpine <span class="hljs-keyword">as</span> builder
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">2</span>/<span class="hljs-number">13</span> : RUN apk add --no-cache python make g++
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">197056</span>ec1964
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">197056</span>ec1964
#  ---&gt; <span class="hljs-number">6609935</span>fe50b
# Step <span class="hljs-number">3</span>/<span class="hljs-number">13</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">17010</span>f65c5e7
# Removing intermediate container <span class="hljs-number">17010</span>f65c5e7
#  ---&gt; b10d12e676ad
# Step <span class="hljs-number">4</span>/<span class="hljs-number">13</span> : COPY ./package.json .
#  ---&gt; <span class="hljs-number">600</span>d31d9362e
# Step <span class="hljs-number">5</span>/<span class="hljs-number">13</span> : RUN npm install
#  ---&gt; Running <span class="hljs-keyword">in</span> a14afc8c0743
### LONG INSTALLATION STUFF GOES HERE ###
#  Removing intermediate container a14afc8c0743
#  ---&gt; <span class="hljs-number">952</span>d5d86e361
# Step <span class="hljs-number">6</span>/<span class="hljs-number">13</span> : FROM node:lts-alpine
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">7</span>/<span class="hljs-number">13</span> : ENV NODE_ENV=development
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>d5376a9e78a
# Removing intermediate container <span class="hljs-number">0</span>d5376a9e78a
#  ---&gt; <span class="hljs-number">910</span>c081ce5f5
# Step <span class="hljs-number">8</span>/<span class="hljs-number">13</span> : USER node
#  ---&gt; Running <span class="hljs-keyword">in</span> cfaefceb1eff
# Removing intermediate container cfaefceb1eff
#  ---&gt; <span class="hljs-number">1480176</span>a1058
# Step <span class="hljs-number">9</span>/<span class="hljs-number">13</span> : RUN mkdir -p /home/node/app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">3</span>ae30e6fb8b8
# Removing intermediate container <span class="hljs-number">3</span>ae30e6fb8b8
#  ---&gt; c391cee4b92c
# Step <span class="hljs-number">10</span>/<span class="hljs-number">13</span> : WORKDIR /home/node/app
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">6</span>aa27f6b50c1
# Removing intermediate container <span class="hljs-number">6</span>aa27f6b50c1
#  ---&gt; <span class="hljs-number">761</span>a7435dbca
# Step <span class="hljs-number">11</span>/<span class="hljs-number">13</span> : COPY . .
#  ---&gt; b5d5c5bdf3a6
# Step <span class="hljs-number">12</span>/<span class="hljs-number">13</span> : COPY --<span class="hljs-keyword">from</span>=builder /app/node_modules /home/node/app/node_modules
#  ---&gt; <span class="hljs-number">9e1</span>a19960420
# Step <span class="hljs-number">13</span>/<span class="hljs-number">13</span> : CMD [ <span class="hljs-string">"./node_modules/.bin/nodemon"</span>, <span class="hljs-string">"--config"</span>, <span class="hljs-string">"nodemon.json"</span>, <span class="hljs-string">"bin/www"</span> ]
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">5</span>bdd62236994
# Removing intermediate container <span class="hljs-number">5</span>bdd62236994
#  ---&gt; <span class="hljs-number">548e178</span>f1386
# Successfully built <span class="hljs-number">548e178</span>f1386
# Successfully tagged notes-api:dev
# Creating notes-api-dev ... done
# Creating notes-db-dev  ... done
</code></pre><p>The <code>--detach</code> or <code>-d</code> option here functions the same as the one you've seen before. The <code>--file</code> or <code>-f</code> option is only needed if the YAML file is not named <code>docker-compose.yaml</code> (but I've used here for demonstration purposes).</p>
<p>Apart from the the <code>up</code> command there is the <code>start</code> command. The main difference between these two is that the <code>start</code> command doesn't create missing containers, only starts existing containers. It's basically the same as the <code>container start</code> command.</p>
<p>The <code>--build</code> option for the <code>up</code> command forces a rebuild of the images. There are some other options for the <code>up</code> command that you can see in the official <a target="_blank" href="https://docs.docker.com/compose/reference/up/">docs</a>.</p>
<h3 id="heading-how-to-list-services-in-docker-compose">How to List Services in Docker Compose</h3>
<p>Although service containers started by Compose can be listed using the <code>container ls</code> command, there is the <code>ps</code> command for listing containers defined in the YAML only.</p>
<pre><code>docker-compose ps

#     Name                   Command               State           Ports         
# -------------------------------------------------------------------------------
# notes-api-dev   docker-entrypoint.sh ./nod ...   Up      <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">3000</span>-&gt;<span class="hljs-number">3000</span>/tcp
# notes-db-dev    docker-entrypoint.sh postgres    Up      <span class="hljs-number">5432</span>/tcp
</code></pre><p>It's not as informative as the <code>container ls</code> output, but it's useful when you have tons of containers running simultaneously.</p>
<h3 id="heading-how-to-execute-commands-inside-a-running-service-in-docker-compose">How to Execute Commands Inside a Running Service in Docker Compose</h3>
<p>I hope you remember from the previous section that you have to run some migration scripts to create the database tables for this API. </p>
<p>Just like the <code>container exec</code> command, there is an <code>exec</code> command for <code>docker-compose</code>. Generic syntax for the command is as follows:</p>
<pre><code>docker-compose exec &lt;service name&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">command</span>&gt;</span></span>
</code></pre><p>To execute the <code>npm run db:migrate</code> command inside the <code>api</code> service, you can execute the following command:</p>
<pre><code>docker-compose exec api npm run db:migrate

# &gt; notes-api@ db:migrate /home/node/app
# &gt; knex migrate:latest
# 
# Using environment: development
# Batch <span class="hljs-number">1</span> run: <span class="hljs-number">1</span> migrations
</code></pre><p>Unlike the <code>container exec</code> command, you don't need to pass the <code>-it</code> flag for interactive sessions. <code>docker-compose</code> does that automatically.</p>
<h3 id="heading-how-to-access-logs-from-a-running-service-in-docker-compose">How to Access Logs from a Running Service in Docker Compose</h3>
<p>You can also use the <code>logs</code> command to retrieve logs from a running service. The generic syntax for the command is as follows:</p>
<pre><code>docker-compose logs &lt;service name&gt;
</code></pre><p>To access the logs from the <code>api</code> service, execute the following command:</p>
<pre><code>docker-compose logs api

# Attaching to notes-api-dev
# notes-api-dev | [nodemon] <span class="hljs-number">2.0</span><span class="hljs-number">.7</span>
# notes-api-dev | [nodemon] reading config ./nodemon.json
# notes-api-dev | [nodemon] to restart at any time, enter <span class="hljs-string">`rs`</span>
# notes-api-dev | [nodemon] or send SIGHUP to <span class="hljs-number">1</span> to restart
# notes-api-dev | [nodemon] ignoring: *.test.js
# notes-api-dev | [nodemon] watching path(s): *.*
# notes-api-dev | [nodemon] watching extensions: js,mjs,json
# notes-api-dev | [nodemon] starting <span class="hljs-string">`node bin/www`</span>
# notes-api-dev | [nodemon] forking
# notes-api-dev | [nodemon] child pid: <span class="hljs-number">19</span>
# notes-api-dev | [nodemon] watching <span class="hljs-number">18</span> files
# notes-api-dev | app running -&gt; http:<span class="hljs-comment">//127.0.0.1:3000</span>
</code></pre><p>This is just a portion from the log output. You can kind of hook into the output stream of the service and get the logs in real-time by using the <code>-f</code> or <code>--follow</code> option. Any later log will show up instantly in the terminal as long as you don't exit by pressing <code>ctrl + c</code> or closing the window. The container will keep running even if you exit out of the log window.</p>
<h3 id="heading-how-to-stop-services-in-docker-compose">How to Stop Services in Docker Compose</h3>
<p>To stop services, there are two approaches that you can take. The first one is the <code>down</code> command. The <code>down</code> command stops all running containers and removes them from the system. It also removes any networks:</p>
<pre><code>docker-compose down --volumes

# Stopping notes-api-dev ... done
# Stopping notes-db-dev  ... done
# Removing notes-api-dev ... done
# Removing notes-db-dev  ... done
# Removing network notes-api_default
# Removing volume notes-db-dev-data
</code></pre><p>The <code>--volumes</code> option indicates that you want to remove any named volume(s) defined in the <code>volumes</code> block. You can learn about the additional options for the <code>down</code> command in the official <a target="_blank" href="https://docs.docker.com/compose/reference/down/">docs</a>.</p>
<p>Another command for stopping services is the <code>stop</code> command which functions identically to the <code>container stop</code> command. It stops all the containers for the application and keeps them. These containers can later be started with the <code>start</code> or <code>up</code> command.</p>
<h3 id="heading-how-to-compose-a-full-stack-application-in-docker-compose">How to Compose a Full-stack Application in Docker Compose</h3>
<p>In this sub-section, we'll be adding a front-end to our notes API and turning it into a complete full-stack application. I won't be explaining any of the <code>Dockerfile.dev</code> files in this sub-section (except the one for the <code>nginx</code> service) as they are identical to some of the others you've already seen in previous sub-sections.‌</p>
<p>If you've cloned the project code repository, then go inside the <code>fullstack-notes-application</code> directory. Each directory inside the project root contains the code for each service and the corresponding <code>Dockerfile</code>.‌</p>
<p>Before we start with the <code>docker-compose.yaml</code> file let's look at a diagram of how the application is going to work:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/fullstack-application-design.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Instead of accepting requests directly like we previously did, in this application all the requests will be first received by an NGINX (lets call it router) service. </p>
<p>The router will then see if the requested end-point has <code>/api</code> in it. If yes, the router will route the request to the back-end or if not, the router will route the request to the front-end.</p>
<p>You do this because when you run a front-end application it doesn't run inside a container. It runs on the browser, served from a container. As a result, Compose networking doesn't work as expected and the front-end application fails to find the <code>api</code> service.</p>
<p>NGINX, on the other hand, runs inside a container and can communicate with the different services across the entire application.</p>
<p>I will not get into the configuration of NGINX here. That topic is kinda out of the scope of this book. But if you want to have a look at it, go ahead and check out the <code>/notes-api/nginx/development.conf</code> and <code>/notes-api/nginx/production.conf</code> files. Code for the <code>/notes-api/nginx/Dockerfile.dev</code> is as follows:</p>
<pre><code>FROM nginx:stable-alpine

COPY ./development.conf /etc/nginx/conf.d/<span class="hljs-keyword">default</span>.conf
</code></pre><p>All it does is copy the configuration file to <code>/etc/nginx/conf.d/default.conf</code> inside the container.</p>
<p>Let's start writing the <code>docker-compose.yaml</code> file. Apart from the <code>api</code> and <code>db</code> services there will be the <code>client</code> and <code>nginx</code> services. There will also be some network definitions that I'll get into shortly.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>

<span class="hljs-attr">services:</span> 
    <span class="hljs-attr">db:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-db-dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">db-data:/var/lib/postgresql/data</span>
        <span class="hljs-attr">environment:</span>
            <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">secret</span>
        <span class="hljs-attr">networks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">backend</span>
    <span class="hljs-attr">api:</span>
        <span class="hljs-attr">build:</span> 
            <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">notes-api:dev</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-api-dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./api:/home/node/app</span>
        <span class="hljs-attr">environment:</span> 
            <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">db</span> <span class="hljs-comment">## same as the database service name</span>
            <span class="hljs-attr">DB_PORT:</span> <span class="hljs-number">5432</span>
            <span class="hljs-attr">DB_USER:</span> <span class="hljs-string">postgres</span>
            <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">DB_PASSWORD:</span> <span class="hljs-string">secret</span>
        <span class="hljs-attr">networks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">backend</span>
    <span class="hljs-attr">client:</span>
        <span class="hljs-attr">build:</span>
            <span class="hljs-attr">context:</span> <span class="hljs-string">./client</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">notes-client:dev</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-client-dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./client:/home/node/app</span>
        <span class="hljs-attr">networks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">frontend</span>
    <span class="hljs-attr">nginx:</span>
        <span class="hljs-attr">build:</span>
            <span class="hljs-attr">context:</span> <span class="hljs-string">./nginx</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">notes-router:dev</span>
        <span class="hljs-attr">container_name:</span> <span class="hljs-string">notes-router-dev</span>
        <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
        <span class="hljs-attr">ports:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-number">8080</span><span class="hljs-string">:80</span>
        <span class="hljs-attr">networks:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">backend</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">frontend</span>

<span class="hljs-attr">volumes:</span>
    <span class="hljs-attr">db-data:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">notes-db-dev-data</span>

<span class="hljs-attr">networks:</span> 
    <span class="hljs-attr">frontend:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">fullstack-notes-application-network-frontend</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
    <span class="hljs-attr">backend:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">fullstack-notes-application-network-backend</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
</code></pre>
<p>The file is almost identical to the previous one you worked with. The only thing that needs some explanation is the network configuration. The code for the <code>networks</code> block is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">networks:</span> 
    <span class="hljs-attr">frontend:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">fullstack-notes-application-network-frontend</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
    <span class="hljs-attr">backend:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">fullstack-notes-application-network-backend</span>
        <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
</code></pre>
<p>I've defined two bridge networks. By default, Compose creates a bridge network and attaches all containers to that. In this project, however, I wanted proper network isolation. So I defined two networks, one for the front-end services and one for the back-end  services.</p>
<p>I've also added <code>networks</code> block in each of the service definitions. This way the the <code>api</code> and <code>db</code> service will be attached to one network and the <code>client</code> service will be attached to a separate network. But the <code>nginx</code> service will be attached to both the networks so that it can perform as router between the front-end and back-end services.</p>
<p>Start all the services by executing the following command:</p>
<pre><code>docker-compose --file docker-compose.yaml up --detach

# Creating network <span class="hljs-string">"fullstack-notes-application-network-backend"</span> <span class="hljs-keyword">with</span> driver <span class="hljs-string">"bridge"</span>
# Creating network <span class="hljs-string">"fullstack-notes-application-network-frontend"</span> <span class="hljs-keyword">with</span> driver <span class="hljs-string">"bridge"</span>
# Creating volume <span class="hljs-string">"notes-db-dev-data"</span> <span class="hljs-keyword">with</span> <span class="hljs-keyword">default</span> driver
# Building api
# Sending build context to Docker daemon  <span class="hljs-number">37.38</span>kB
# 
# Step <span class="hljs-number">1</span>/<span class="hljs-number">13</span> : FROM node:lts-alpine <span class="hljs-keyword">as</span> builder
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">2</span>/<span class="hljs-number">13</span> : RUN apk add --no-cache python make g++
#  ---&gt; Running <span class="hljs-keyword">in</span> <span class="hljs-number">8</span>a4485388fd3
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container <span class="hljs-number">8</span>a4485388fd3
#  ---&gt; <span class="hljs-number">47</span>fb1ab07cc0
# Step <span class="hljs-number">3</span>/<span class="hljs-number">13</span> : WORKDIR /app
#  ---&gt; Running <span class="hljs-keyword">in</span> bc76cc41f1da
# Removing intermediate container bc76cc41f1da
#  ---&gt; <span class="hljs-number">8</span>c03fdb920f9
# Step <span class="hljs-number">4</span>/<span class="hljs-number">13</span> : COPY ./package.json .
#  ---&gt; a1d5715db999
# Step <span class="hljs-number">5</span>/<span class="hljs-number">13</span> : RUN npm install
#  ---&gt; Running <span class="hljs-keyword">in</span> fabd33cc0986
### LONG INSTALLATION STUFF GOES HERE ###
# Removing intermediate container fabd33cc0986
#  ---&gt; e09913debbd1
# Step <span class="hljs-number">6</span>/<span class="hljs-number">13</span> : FROM node:lts-alpine
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">7</span>/<span class="hljs-number">13</span> : ENV NODE_ENV=development
#  ---&gt; Using cache
#  ---&gt; b7c12361b3e5
# Step <span class="hljs-number">8</span>/<span class="hljs-number">13</span> : USER node
#  ---&gt; Using cache
#  ---&gt; f5ac66ca07a4
# Step <span class="hljs-number">9</span>/<span class="hljs-number">13</span> : RUN mkdir -p /home/node/app
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">60094</span>b9a6183
# Step <span class="hljs-number">10</span>/<span class="hljs-number">13</span> : WORKDIR /home/node/app
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">316</span>a252e6e3e
# Step <span class="hljs-number">11</span>/<span class="hljs-number">13</span> : COPY . .
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">3</span>a083622b753
# Step <span class="hljs-number">12</span>/<span class="hljs-number">13</span> : COPY --<span class="hljs-keyword">from</span>=builder /app/node_modules /home/node/app/node_modules
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">707979</span>b3371c
# Step <span class="hljs-number">13</span>/<span class="hljs-number">13</span> : CMD [ <span class="hljs-string">"./node_modules/.bin/nodemon"</span>, <span class="hljs-string">"--config"</span>, <span class="hljs-string">"nodemon.json"</span>, <span class="hljs-string">"bin/www"</span> ]
#  ---&gt; Using cache
#  ---&gt; f2da08a5f59b
# Successfully built f2da08a5f59b
# Successfully tagged notes-api:dev
# Building client
# Sending build context to Docker daemon  <span class="hljs-number">43.01</span>kB
# 
# Step <span class="hljs-number">1</span>/<span class="hljs-number">7</span> : FROM node:lts-alpine
#  ---&gt; <span class="hljs-number">471e8</span>b4eb0b2
# Step <span class="hljs-number">2</span>/<span class="hljs-number">7</span> : USER node
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">4</span>be5fb31f862
# Step <span class="hljs-number">3</span>/<span class="hljs-number">7</span> : RUN mkdir -p /home/node/app
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">1</span>fefc7412723
# Step <span class="hljs-number">4</span>/<span class="hljs-number">7</span> : WORKDIR /home/node/app
#  ---&gt; Using cache
#  ---&gt; d1470d878aa7
# Step <span class="hljs-number">5</span>/<span class="hljs-number">7</span> : COPY ./package.json .
#  ---&gt; Using cache
#  ---&gt; bbcc49475077
# Step <span class="hljs-number">6</span>/<span class="hljs-number">7</span> : RUN npm install
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">860</span>a4a2af447
# Step <span class="hljs-number">7</span>/<span class="hljs-number">7</span> : CMD [ <span class="hljs-string">"npm"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"serve"</span> ]
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">11</span>db51d5bee7
# Successfully built <span class="hljs-number">11</span>db51d5bee7
# Successfully tagged notes-client:dev
# Building nginx
# Sending build context to Docker daemon   <span class="hljs-number">5.12</span>kB
# 
# Step <span class="hljs-number">1</span>/<span class="hljs-number">2</span> : FROM nginx:stable-alpine
#  ---&gt; f2343e2e2507
# Step <span class="hljs-number">2</span>/<span class="hljs-number">2</span> : COPY ./development.conf /etc/nginx/conf.d/<span class="hljs-keyword">default</span>.conf
#  ---&gt; Using cache
#  ---&gt; <span class="hljs-number">02</span>a55d005a98
# Successfully built <span class="hljs-number">02</span>a55d005a98
# Successfully tagged notes-router:dev
# Creating notes-client-dev ... done
# Creating notes-api-dev    ... done
# Creating notes-router-dev ... done
# Creating notes-db-dev     ... done
</code></pre><p>Now visit <code>http://localhost:8080</code> and voilà!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/01/notes-application.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Try adding and deleting notes to see if the application works properly. The project also comes with shell scripts and a <code>Makefile</code>. Explore them to see how you can run this project without the help of <code>docker-compose</code> like you did in the previous section.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I would like to thank you from the bottom of my heart for the time you've spent reading this book. I hope you've enjoyed it and have learned all the essentials of Docker.</p>
<p>Apart from this one, I've written full-length handbooks on other complicated topics available for free on <a target="_blank" href="https://www.freecodecamp.org/news/author/farhanhasin/">freeCodeCamp</a>.</p>
<p>These handbooks are part of my mission to simplify hard to understand technologies for everyone. Each of these handbooks takes a lot of time and effort to write.</p>
<p>If you've enjoyed my writing and want to keep me motivated, consider leaving starts on <a target="_blank" href="https://github.com/fhsinchy/">GitHub</a> and endorse me for relevant skills on <a target="_blank" href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. I also accept sponsorship so you may consider <a target="_blank" href="https://www.buymeacoffee.com/farhanhasin">buying me a coffee</a> if you want to.</p>
<p>I'm always open to suggestions and discussions on <a target="_blank" href="https://twitter.com/frhnhsin">Twitter</a> or <a target="_blank" href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. Hit me with direct messages.</p>
<p>In the end, consider sharing the resources with others, because </p>
<blockquote>
<p>Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something. — Richard Stallman</p>
</blockquote>
<p>Till the next one, stay safe and keep learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Kubernetes Handbook – Learn Kubernetes for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Kubernetes is an open-source container orchestration platform that automates the deployment, management, scaling, and networking of containers.  It was developed by Google using the Go Programming Language, and this amazing technology has been open-s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-kubernetes-handbook/</link>
                <guid isPermaLink="false">66b0ab547cd8dca6718a22bf</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Farhan Hasin Chowdhury ]]>
                </dc:creator>
                <pubDate>Thu, 20 Aug 2020 15:53:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/Kubernetes-Handbook-Mockup.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="https://kubernetes.io/">Kubernetes</a> is an open-source container orchestration platform that automates the deployment, management, scaling, and networking of containers. </p>
<p>It was developed by <a target="_blank" href="https://opensource.google/projects/kubernetes">Google</a> using the <a target="_blank" href="https://golang.org/">Go Programming Language</a>, and this amazing technology has been open-source since 2014.</p>
<p>According to the <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#overview">Stack Overflow Developer Survey - 2020</a>, Kubernetes is the <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-loved5">#3 most loved platform</a> and <a target="_blank" href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-platforms-wanted5">#3 most wanted platform</a>.</p>
<p>Apart from being very powerful, Kubernetes is known for being quite hard to get started with. I won't say it's easy, but if you are equipped with the prerequisites and go through this guide attentively and with patience, you should be able to:</p>
<ul>
<li>Get a solid understanding of the fundamentals.</li>
<li>Create and manage Kubernetes clusters.</li>
<li>Deploy (almost) any application to a Kubernetes cluster.</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>Familiarity with JavaScript</li>
<li>Familiarity with the Linux Terminal</li>
<li>Familiarity with Docker (suggested read: <a target="_blank" href="https://www.freecodecamp.org/news/the-docker-handbook/">The Docker Handbook</a>)</li>
</ul>
<h2 id="heading-project-code">Project Code</h2>
<p>Code for the example projects can be found in the following repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/fhsinchy/kubernetes-handbook-projects">https://github.com/fhsinchy/kubernetes-handbook-projects</a></div>
<p>You can find the complete code in the <code>completed</code> branch.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-introduction-to-container-orchestration-and-kubernetes">Introduction to Container Orchestration and Kubernetes</a></li>
<li><a class="post-section-overview" href="#heading-installing-kubernetes">Installing Kubernetes</a></li>
<li><a class="post-section-overview" href="#heading-hello-world-in-kubernetes">Hello World in Kubernetes</a><ul>
<li><a class="post-section-overview" href="#heading-kubernetes-architecture">Kubernetes Architecture</a></li>
<li><a class="post-section-overview" href="#heading-control-plane-components">Control Plane Components</a></li>
<li><a class="post-section-overview" href="#heading-node-components">Node Components</a></li>
<li><a class="post-section-overview" href="#heading-kubernetes-objects">Kubernetes Objects</a></li>
<li><a class="post-section-overview" href="#heading-pods">Pods</a></li>
<li><a class="post-section-overview" href="#heading-services">Services</a></li>
<li><a class="post-section-overview" href="#heading-the-full-picture-1">The Full Picture</a></li>
<li><a class="post-section-overview" href="#heading-getting-rid-of-kubernetes-resources">Getting Rid of Kubernetes Resources</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-declarative-deployment-approach">Declarative Deployment Approach</a><ul>
<li><a class="post-section-overview" href="#heading-writing-your-first-set-of-configurations">Writing Your First Set of Configurations</a></li>
<li><a class="post-section-overview" href="#heading-the-kubernetes-dashboard">The Kubernetes Dashboard</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-working-with-multi-container-applications">Working with Multi-Container Applications</a><ul>
<li><a class="post-section-overview" href="#heading-deployment-plan">Deployment Plan</a></li>
<li><a class="post-section-overview" href="#heading-replication-controllers-replica-sets-and-deployments">Replication Controllers, Replica Sets, and Deployments</a></li>
<li><a class="post-section-overview" href="#heading-creating-your-first-deployment">Creating Your First Deployment</a></li>
<li><a class="post-section-overview" href="#heading-inspecting-kubernetes-resources">Inspecting Kubernetes Resources</a></li>
<li><a class="post-section-overview" href="#heading-getting-container-logs-from-pods">Getting Container Logs from Pods</a></li>
<li><a class="post-section-overview" href="#heading-environment-variables">Environment Variables</a></li>
<li><a class="post-section-overview" href="#heading-creating-the-database-deployment">Creating The Database Deployment</a></li>
<li><a class="post-section-overview" href="#heading-persistent-volumes-and-persistent-volume-claims">Persistent Volumes and Persistent Volume Claims</a></li>
<li><a class="post-section-overview" href="#heading-dynamic-provisioning-of-persistent-volumes">Dynamic Provisioning of Persistent Volumes</a></li>
<li><a class="post-section-overview" href="#heading-connecting-volumes-with-pods">Connecting Volumes with Pods</a></li>
<li><a class="post-section-overview" href="#heading-wiring-everything-up">Wiring Everything Up</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-working-with-ingress-controllers">Working with Ingress Controllers</a><ul>
<li><a class="post-section-overview" href="#heading-setting-up-nginx-ingress-controller">Setting-up NGINX Ingress Controller</a></li>
<li><a class="post-section-overview" href="#heading-secrets-and-config-maps-in-kubernetes">Secrets and Config Maps in Kubernetes</a></li>
<li><a class="post-section-overview" href="#heading-performing-update-rollouts-in-kubernetes">Performing Update Rollouts in Kubernetes</a></li>
<li><a class="post-section-overview" href="#heading-combining-configurations">Combining Configurations</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-troubleshooting-1">Troubleshooting</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-introduction-to-container-orchestration-and-kubernetes">Introduction to Container Orchestration and Kubernetes</h2>
<p>According to <a target="_blank" href="https://www.redhat.com/en/topics/containers/what-is-container-orchestration">Red Hat</a> — </p>
<blockquote>
<p>"Container orchestration is the process of automating the deployment, management, scaling, and networking tasks of containers.   </p>
<p>It can be used in any environment where you use containers and can help you deploy the same application across different environments without requiring any redesigning".</p>
</blockquote>
<p>Let me show you an example. Assume that you have developed an amazing application that suggests to people what they should eat depending on the time of day. </p>
<p>Now assume that you've containerized the application using Docker and deployed it on AWS.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/food-suggestion-application-single-instance.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If the application goes down for any reason, the users lose access to your service immediately. </p>
<p>To solve this issue, you can make multiple copies or replicas of the same application and make it highly available.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/food-suggestion-application-multi-instance.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Even if one of the instances goes down, the other two will be available to the users. </p>
<p>Now assume that your application has become wildly popular among the night owls and your servers are being flooded with requests at night, while you're sleeping. </p>
<p>What if all the instances go down due to overload? Who's going to do the scaling? Even if you scale up and make 50 replicas of your application, who's going to check on their health? How are going to set-up the networking so that requests hit the right endpoint? Load balancing is going to be a big concern as well, isn't it?</p>
<p>Kubernetes can make things much easier for these kinds of situations. It's a container orchestration platform that consists of several components and it works tirelessly to keep your servers in the state that you desire. </p>
<p>Assume that you want to have 50 replicas of your application running continuously. Even if there is a sudden rise in the user count, the server needs to be scaled up automatically. </p>
<p>You just tell your needs to Kubernetes and it will do the rest of the heavy lifting for you.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/kube-representation.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Kubernetes will not only implement the state, it'll also maintain it. It will make additional replicas if any of the old ones dies, manage the networking and storage, rollout or rollback updates, or even upscale the server if ever necessary.</p>
<h2 id="heading-installing-kubernetes">Installing Kubernetes</h2>
<p>Running Kubernetes in your local machine is actually a lot different than running Kubernetes on the cloud. To get Kubernetes up and running, you need two programs.</p>
<ul>
<li><a target="_blank" href="https://kubernetes.io/docs/tasks/tools/install-minikube/">minikube</a> - it runs a single-node Kubernetes cluster inside a Virtual Machine (VM) on your local computer.</li>
<li><a target="_blank" href="https://kubernetes.io/docs/tasks/tools/install-kubectl/">kubectl</a> - The Kubernetes command-line tool, which allows you to run commands against Kubernetes clusters.</li>
</ul>
<p>Apart from these two programs, you'll also need a hypervisor and a containerization platform. <a target="_blank" href="https://www.docker.com/">Docker</a> is the obvious choice for the containerization platform. Recommended hypervisors are as follows:</p>
<ul>
<li><a target="_blank" href="https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v">Hyper-V</a> for Windows</li>
<li><a target="_blank" href="https://github.com/moby/hyperkit">HyperKit</a> for Mac</li>
<li><a target="_blank" href="https://www.docker.com/">Docker</a> for Linux</li>
</ul>
<p>Hyper-V comes built into Windows 10 (Pro, Enterprise, and Education) as an optional feature and can be turned on from the control panel. </p>
<p>HyperKit comes bundled with Docker Desktop for Mac as a core component. </p>
<p>And on Linux, you can bypass the entire hypervisor layer by using Docker directly. It's much faster than using any hypervisor and is the recommended way to run Kubernetes on Linux.</p>
<p>You may go ahead and install any of the above mentioned hypervisors. Or if you want to keep things simple, just get <a target="_blank" href="https://www.virtualbox.org/">VirtualBox</a>. </p>
<p>For the rest of the article, I'll assume that you're using VirtualBox. Don't worry though, even if you're using something else, there shouldn't be that much of a difference.</p>
<blockquote>
<p>I'll be using <code>minikube</code> with the Docker driver on a <a target="_blank" href="https://www.freecodecamp.org/news/p/c4f90e6f-97af-41ce-b775-b6e52a5a5152/ubuntu.com/">Ubuntu</a> machine throughout the entire article.</p>
</blockquote>
<p>Once you have installed the hypervisor and the containerization platform, it's time to install the <code>minikube</code> and <code>kubectl</code> programs. </p>
<p><code>kubectl</code> usually comes bundled with Docker Desktop on Mac and Windows. Installation instructions for Linux can be found <a target="_blank" href="https://kubernetes.io/docs/tasks/tools/install-kubectl/">here</a>. </p>
<p><code>minikube</code>, on the other hand, has to be installed on all three of the systems. You can use <a target="_blank" href="https://brew.sh/">Homebrew</a> on Mac, and <a target="_blank" href="https://chocolatey.org/">Chocolatey</a> on Windows to install <code>minikube</code>. Installation instructions for Linux can be found <a target="_blank" href="https://kubernetes.io/docs/tasks/tools/install-minikube/">here</a>.</p>
<p>Once you've installed them, you can test out both programs by executing the following commands:</p>
<pre><code class="lang-bash">minikube version

<span class="hljs-comment"># minikube version: v1.12.1</span>
<span class="hljs-comment"># commit: 5664228288552de9f3a446ea4f51c6f29bbdd0e0</span>

kubectl version

<span class="hljs-comment"># Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.6", GitCommit:"dff82dc0de47299ab66c83c626e08b245ab19037", GitTreeState:"clean", BuildDate:"2020-07-16T00:04:31Z", GoVersion:"go1.14.4", Compiler:"gc", Platform:"darwin/amd64"}</span>
<span class="hljs-comment"># Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.3", GitCommit:"2e7996e3e2712684bc73f0dec0200d64eec7fe40", GitTreeState:"clean", BuildDate:"2020-05-20T12:43:34Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}</span>
</code></pre>
<p>If you've downloaded the right versions for your operating system and have set up the paths properly, you should be ready to go.</p>
<p>As I've already mentioned, <code>minikube</code> runs a single-node Kubernetes cluster inside a Virtual Machine (VM) on your local computer. I'll explain clusters and nodes in greater details in an upcoming section. </p>
<p>For now, understand that <code>minikube</code> creates a regular VM using your hypervisor of choice and treats that as a Kubernetes cluster.</p>
<blockquote>
<p>If you face any problems in this section please have a look at the <a class="post-section-overview" href="#heading-troubleshooting-1">Troubleshooting</a> section at the end of this article.</p>
</blockquote>
<p>Before you start <code>minikube</code>, you have to set the correct hypervisor driver for it to use. To set VirtualBox as the default driver, execute the following command:</p>
<pre><code class="lang-bash">minikube config <span class="hljs-built_in">set</span> driver virtualbox

<span class="hljs-comment"># ❗ These changes will take effect upon a minikube delete and then a minikube start</span>
</code></pre>
<p>You can replace <code>virtualbox</code> with <code>hyperv</code>, <code>hyperkit</code>, or <code>docker</code> as per your preference. This command is necessary for the first time only. </p>
<p>To start <code>minikube</code>, execute the following command:</p>
<pre><code class="lang-bash">minikube start

<span class="hljs-comment"># ? minikube v1.12.1 on Ubuntu 20.04</span>
<span class="hljs-comment"># ✨ Using the virtualbox driver based on existing profile</span>
<span class="hljs-comment"># ? Starting control plane node minikube in cluster minikube</span>
<span class="hljs-comment"># ? Updating the running virtualbox "minikube" VM ...</span>
<span class="hljs-comment"># ? Preparing Kubernetes v1.18.3 on Docker 19.03.12 ...</span>
<span class="hljs-comment"># ? Verifying Kubernetes components...</span>
<span class="hljs-comment"># ? Enabled addons: default-storageclass, storage-provisioner</span>
<span class="hljs-comment"># ? Done! kubectl is now configured to use "minikube"</span>
</code></pre>
<p>You can stop <code>minikube</code> by executing the <code>minikube stop</code> command.</p>
<h2 id="heading-hello-world-in-kubernetes">Hello World in Kubernetes</h2>
<p>Now that you have Kubernetes on your local system, it's time to get your hands dirty. In this example you'll be deploying a very simple application to your local cluster and getting familiar with the fundamentals.</p>
<blockquote>
<p>There will be terminologies like <strong>pod</strong>, <strong>service</strong>, <strong>load balancer</strong>, and so on in this section. Don't stress if you don't understand them right away. I'll go into great details explaining each of them in <a class="post-section-overview" href="#heading-the-full-picture-1">The Full Picture</a> sub-section.</p>
</blockquote>
<p>If you've started <code>minikube</code> in the previous section then you're ready to go. Otherwise you'll have to start it now. Once <code>minikube</code> has started, execute the following command in your terminal:</p>
<pre><code class="lang-bash">kubectl run hello-kube --image=fhsinchy/hello-kube --port=80

<span class="hljs-comment"># pod/hello-kube created</span>
</code></pre>
<p>You'll see the <code>pod/hello-kube created</code> message almost immediately. The <a target="_blank" href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#run">run</a> command runs the given container image inside a pod. </p>
<p>Pods are like a box that encapsulates a container. To make sure the pod has been created and is running, execute the following command:</p>
<pre><code class="lang-bash">kubectl get pod

<span class="hljs-comment"># NAME         READY   STATUS    RESTARTS   AGE</span>
<span class="hljs-comment"># hello-kube   1/1     Running   0          3m3s</span>
</code></pre>
<p>You should see <code>Running</code> in the <code>STATUS</code> column. If you see something like <code>ContainerCreating</code>, wait for a minute or two and check again. </p>
<p>Pods by default are inaccessible from outside the cluster. To make them accessible, you have to expose them using a service. So, once the pod is up and running, execute the following command to expose the pod:</p>
<pre><code class="lang-bash">kubectl expose pod hello-kube --<span class="hljs-built_in">type</span>=LoadBalancer --port=80

<span class="hljs-comment"># service/hello-kube exposed</span>
</code></pre>
<p>To make sure the load balancer service has been created successfully, execute the following command:</p>
<pre><code class="lang-bash">kubectl get service

<span class="hljs-comment"># NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE</span>
<span class="hljs-comment"># hello-kube   LoadBalancer   10.109.60.75   &lt;pending&gt;     80:30848/TCP   119s</span>
<span class="hljs-comment"># kubernetes   ClusterIP      10.96.0.1      &lt;none&gt;        443/TCP        7h47m</span>
</code></pre>
<p>Make sure you see the <code>hello-kube</code> service in the list. Now that you have a pod running that is exposed, you can go ahead and access that. Execute the following command to do so:</p>
<pre><code class="lang-bash">minikube service hello-kube

<span class="hljs-comment"># |-----------|------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># | NAMESPACE |    NAME    | TARGET PORT |             URL             |</span>
<span class="hljs-comment"># |-----------|------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># | default   | hello-kube |          80 | http://192.168.99.101:30848 |</span>
<span class="hljs-comment"># |-----------|------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># ? Opening service default/hello-kube in default browser...</span>
</code></pre>
<p>Your default web browser should open automatically and you should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-85.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This is a very simple JavaScript application that I've put together using <a target="_blank" href="https://github.com/vitejs/vite">vite</a> and a little bit of CSS. To understand what you just did, you have to gain a good understanding of the Kubernetes architecture.</p>
<h3 id="heading-kubernetes-architecture">Kubernetes Architecture</h3>
<p>In the world of Kubernetes, a <strong>node</strong> can be either a physical or a virtual machine with a given role. A collection of such machines or servers using a shared network to communicate between each other is called a <strong>cluster</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/nodes-cluster-1.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In your local setup, <code>minikube</code> is a single node Kubernetes cluster. So instead of having multiple servers like in the diagram above, <code>minikube</code> has only one that acts as both the main server and the node.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/minikube-1.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Each server in a Kubernetes cluster gets a role. There are two possible roles:</p>
<ul>
<li><strong>control-plane</strong> — Makes most of the necessary decisions and acts as sort of the brains of the entire cluster. This can be a single server or a group of server in larger projects.</li>
<li><strong>node</strong> — Responsible for running workloads. These servers are usually micro managed by the control plane and carries out various tasks following supplied instructions. </li>
</ul>
<p>Every server in you cluster will have a selected set of components. The number and type of those components can vary depending on the role a server has in your cluster. That means the nodes do not have all the components that the control plane has.</p>
<p>In the upcoming subsections, you'll have a more detailed look into the individual components that make up a Kubernetes cluster.</p>
<h3 id="heading-control-plane-components">Control Plane Components</h3>
<p>The control plane in a Kubernetes cluster consists of <strong>five</strong> components. These are as follows:</p>
<ol>
<li><strong>kube-api-server:</strong> This acts as the entrance to the Kubernetes control plane, responsible for validating and processing requests delivered using client libraries like the <code>kubectl</code> program.</li>
<li><strong>etcd:</strong> This is a distributed key-value store which acts as the single source of truth about your cluster. It holds configuration data and information about the state of the cluster. <a target="_blank" href="https://etcd.io/">etcd</a> is an open-source project and is developed by the folks behind Red Hat. The source code of the project is hosted on the <a target="_blank" href="https://github.com/etcd-io/etcd">etcd-io/etcd</a> GitHub repo.</li>
<li><strong>kube-controller-manager:</strong> The controllers in Kubernetes are responsible for controlling the state of the cluster. When you let Kubernetes know what you want in your cluster, the controllers make sure that your request is fulfilled. The <code>kube-controller-manager</code> is all the controller processes grouped into a single process.</li>
<li><strong>kube-scheduler:</strong> Assigning task to a certain node considering its available resources and the requirements of the task is known as scheduling. The <code>kube-scheduler</code> component does the task of scheduling in Kubernetes making sure none of the servers in the cluster is overloaded.</li>
<li><strong>cloud-controller-manager:</strong> In a real world cloud environment, this component lets you wire-up your cluster with your cloud provider's (<a target="_blank" href="https://cloud.google.com/kubernetes-engine">GKE</a>/<a target="_blank" href="https://aws.amazon.com/eks/">EKS</a>) API. This way, the components that interact with that cloud platform stays isolated from components that just interact with your cluster. In a local cluster like <code>minikube</code>, this component doesn't exist.</li>
</ol>
<h3 id="heading-node-components">Node Components</h3>
<p>Compared to the control plane, nodes have a very small number of components. These components are as follows:</p>
<ol>
<li><strong>kubelet:</strong> This service acts as the gateway between the control plain and each of the nodes in a cluster. Every instructions from the control plain towards the nodes, passes through this service. It also interacts with the <code>etcd</code> store to keep the state information updated.</li>
<li><strong>kube-proxy:</strong> This small service runs on each node server and maintains network rules on them. Any network request that reaches a service inside your cluster, passes through this service.</li>
<li><strong>Container Runtime:</strong> Kubernetes is a container orchestration tool hence it runs applications in containers. This means that every node needs to have a container runtime like <a target="_blank" href="https://www.docker.com/">Docker</a> or <a target="_blank" href="https://coreos.com/rkt/">rkt</a> or <a target="_blank" href="https://cri-o.io/">cri-o</a>.</li>
</ol>
<h3 id="heading-kubernetes-objects">Kubernetes Objects</h3>
<p>According to the Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/">documentation</a> — </p>
<blockquote>
<p>"Objects are persistent entities in the Kubernetes system. Kubernetes uses these entities to represent the state of your cluster.  Specifically, they can describe what containerized applications are running, the resources available to them, and the policies around their behaviour."</p>
</blockquote>
<p>When you create a Kubernetes object, you're effectively telling the Kubernetes system that you want this object to exist no matter what and the Kubernetes system will constantly work to keep the object running.</p>
<h3 id="heading-pods">Pods</h3>
<p>According to the Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/pods/">documentation</a> — </p>
<blockquote>
<p>"Pods are the smallest deployable units of computing that you can create and manage in Kubernetes". </p>
</blockquote>
<p>A pod usually encapsulates one or more containers that are closely related sharing a life cycle and consumable resources.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/pods-1.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Although a pod can house more than one container, you shouldn't just put containers in a pod willy nilly. Containers in a pod must be so closely related, that they can be treated as a single application. </p>
<p>As an example, your back-end API may depend on the database but that doesn't mean you'll put both of them in the same pod. Throughout this entire article, you won't see any pod that has more than one container running.</p>
<p>Usually, you should not manage a pod directly. Instead, you should work with higher level objects that can provide you much better manageability. You'll learn about these higher level objects in later sections.</p>
<h3 id="heading-services">Services</h3>
<p>According to the Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/service/">documentation</a> — </p>
<blockquote>
<p>"A service in Kubernetes is an abstract way to expose an application running on a set of pods as a network service".</p>
</blockquote>
<p>Kubernetes pods are ephemeral in nature. They get created and after some time when they get destroyed, they do not get recycled. </p>
<p>Instead new identical pods take the places of the old ones. Some higher level Kubernetes objects are even capable of creating and destroying pods dynamically.</p>
<p>A new IP address is assigned to each pod at the time of their creation. But in case of a high level object that can create, destroy, and group together a number of pods, the set of pods running in one moment in time could be different from the set of pods running that application a moment later.</p>
<p>This leads to a problem: if some set of pods in your cluster depends on another set of pods within your cluster, how do they find out and keep track of each other's IP addresses?</p>
<p>The Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/service/">documentation</a> says — </p>
<blockquote>
<p>"a Service is an abstraction which defines a logical set of Pods and a policy by which to access them". </p>
</blockquote>
<p>Which essentially means that a Service groups together a number of pods that perform the same function and presents them as a single entity. </p>
<p>This way, the confusion of keeping track of multiple pods goes out of the window as that single Service now acts as a sort of communicator for all of them.</p>
<p>In the <code>hello-kube</code> example, you created a <code>LoadBalancer</code> type of service which allows requests from outside the cluster connect to pods running inside the cluster.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/load-balancer-3.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Any time you need to give access to one or more pods to another application or to something outside of the cluster, you should create a service. </p>
<p>For instance, if you have a set of pods running web servers that should be accessible from the internet, a service will provide the necessary abstraction.</p>
<h3 id="heading-the-full-picture">The Full Picture</h3>
<p>Now that you have a proper understanding of the individual Kubernetes components, here is a visual representation of how they work together behind the scenes:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/components-of-kubernetes.png" alt="Image" width="600" height="400" loading="lazy">
<em>https://kubernetes.io/docs/concepts/overview/components/</em></p>
<p>Before I get into explaining the individual details, have a look at what the Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/">documentation</a> has to say — </p>
<blockquote>
<p>"To work with Kubernetes objects – whether to create, modify, or delete them – you'll need to use the <a target="_blank" href="https://kubernetes.io/docs/concepts/overview/kubernetes-api/">Kubernetes API</a>. When you use the <code>kubectl</code> command-line interface, the CLI makes the necessary Kubernetes API calls for you."</p>
</blockquote>
<p>The first command that you ran was the <code>run</code> command. It was as follows:</p>
<pre><code class="lang-bash">kubectl run hello-kube --image=fhsinchy/hello-kube --port=80
</code></pre>
<p>The <code>run</code> command is responsible for creating a new pod that runs the given image. Once you've issued this command, following sets of events occur inside the Kubernetes cluster:</p>
<ul>
<li>The <code>kube-api-server</code> component receives the request, validates it and processes it.</li>
<li>The <code>kube-api-server</code> then communicates with the <code>kubelet</code> component on the node and provides the instructions necessary for creating the pod.</li>
<li>The <code>kubelet</code> component then starts working on making the pod up and running and also keeps the state information updated in the <code>etcd</code> store.</li>
</ul>
<p>Generic syntax for the <code>run</code> command is as follows:</p>
<pre><code>kubectl run &lt;pod name&gt; --image=&lt;image name&gt; --port=&lt;port to expose&gt;
</code></pre><p>You can run any valid container image inside a pod. The <a target="_blank" href="https://hub.docker.com/r/fhsinchy/hello-kube">fhsinchy/hello-kube</a> Docker image contains a very simple JavaScript application that runs on port 80 inside the container. The <code>--port=80</code> option allows the pod to expose port 80 from inside the container.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/pods-2.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The newly created pod runs inside the <code>minikube</code> cluster and is inaccessible from the outside. To expose the pod and make it accessible, the second command that you issued was as follows:</p>
<pre><code>kubectl expose pod hello-kube --type=LoadBalancer --port=<span class="hljs-number">80</span>
</code></pre><p>The <code>expose</code> command is responsible for creating a Kubernetes service of type <code>LoadBalancer</code> that allows users to access the application running inside the pod. </p>
<p>Just like the <code>run</code> command, the <code>expose</code> command execution goes through same sort of steps inside the cluster. But instead of a pod, the <code>kube-api-server</code> provides instructions necessary for creating a service in this case to the <code>kubelet</code> component.</p>
<p>Generic syntax for the <code>expose</code> command is as follows:</p>
<pre><code>kubectl expose &lt;resource kind to expose&gt; &lt;resource name&gt; --type=&lt;type of service to create&gt; --port=&lt;port to expose&gt;
</code></pre><p>The object type can be any valid Kubernetes object type. The name has to match up with the object name you're trying to expose.</p>
<p> <code>--type</code> indicates the type of service you want. There are four different types of services available for internal or external networking. </p>
<p>Lastly, the <code>--port</code> is the port number you want to expose from the running container.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/services-half.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once the service has been created, the last piece of the puzzle was to access the application running inside the pod. To do that, the command you executed was as follows:</p>
<pre><code>minikube service hello-kube
</code></pre><p>Unlike the previous ones, this last command doesn't go to the <code>kube-api-server</code>. Rather it communicates with the local cluster using the <code>minikube</code> program. The <code>service</code> command for <code>minikube</code> returns a full URL for a given service.</p>
<p>When you created the <code>hello-kube</code> pod with the <code>--port=80</code> option, you instructed Kubernetes to let the pod expose port 80 from inside the container but it wasn't accessible from outside the cluster. </p>
<p>Then when you created the <code>LoadBalancer</code> service with the <code>--port=80</code> option, it mapped port 80 from that container to an arbitrary port in the local system making it accessible from outside the cluster.</p>
<p>On my system, the <code>service</code> command returns <code>192.168.99.101:30848</code> URL for the pod. The IP in this URL is actually the IP of the <code>minikube</code> virtual machine. You can verify this by executing the following command:</p>
<pre><code class="lang-bash">minikube ip

<span class="hljs-comment"># 192.168.99.101</span>
</code></pre>
<p>To verify that the <code>30848</code> port points to port 80 inside the pod, you can execute the following command:</p>
<pre><code class="lang-bash">kubectl get service hello-kube

<span class="hljs-comment"># NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE</span>
<span class="hljs-comment"># hello-kube   LoadBalancer   10.109.60.75   &lt;pending&gt;     80:30848/TCP   119s</span>
</code></pre>
<p>On the <code>PORT(S)</code> column, you can see that port <code>80</code> indeed maps to port <code>30484</code> on the local system. So instead of running the <code>service</code> command you can just inspect the IP and port and then put it into your browser manually to access the <code>hello-kube</code> application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-86.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, the final state of the cluster can be visualized as follows:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/services-1.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you're coming from Docker, then the significance of using a service in order to expose a pod may seem a bit too verbose to you at the moment. </p>
<p>But as you go into the examples that deal with more than one pod, you'll start to appreciate everything that Kubernetes has to offer.</p>
<h2 id="heading-getting-rid-of-kubernetes-resources">Getting Rid of Kubernetes Resources</h2>
<p>Now that you know how to create Kubernetes resources like pods and Services, you need to know how to get rid of them. The only way to get rid of a Kubernetes resource is to delete it.</p>
<p>You can do that by using the <code>delete</code> command for <code>kubectl</code>. Generic syntax of the command is as follows:</p>
<pre><code class="lang-bash">kubectl delete &lt;resource <span class="hljs-built_in">type</span>&gt; &lt;resource name&gt;
</code></pre>
<p>To delete a pod named <code>hello-kube</code> the command will be as follows:</p>
<pre><code class="lang-bash">kubectl delete pod hello-kube

<span class="hljs-comment"># pod "hello-kube" deleted</span>
</code></pre>
<p>And to delete a service named <code>hello-kube</code> the command will be as follows:</p>
<pre><code class="lang-bash">kubectl delete service hello-kube

<span class="hljs-comment"># service "hello-kube" deleted</span>
</code></pre>
<p>Or if you're in a destructive mood, you can delete all objects of a kind in one go using the <code>--all</code> option for the <code>delete</code> command. Generic syntax for the option is as follows:</p>
<pre><code class="lang-bash">kubectl delete &lt;object <span class="hljs-built_in">type</span>&gt; --all
</code></pre>
<p>So to delete all pods and services you have to execute <code>kubectl delete pod --all</code> and <code>kubectl delete service --all</code> respectively.</p>
<h2 id="heading-declarative-deployment-approach">Declarative Deployment Approach</h2>
<p>To be honest, the <code>hello-kube</code> example you just saw in the previous section is not an ideal way of performing deployment with Kubernetes. </p>
<p>The approach that you took in that section is an <strong>imperative approach</strong> which means you had to execute every command one after the another manually. Taking an imperative approach defies the entire point of Kubernetes.</p>
<p>An ideal approach to deployment with Kubernetes is the <strong>declarative approach</strong>. In it you, as a developer, let Kubernetes know the state you desire your servers to be in and Kubernetes figures out a way to implement that. </p>
<p>In this section you'll be deploying the same <code>hello-kube</code> application in a declarative approach.</p>
<p>If you haven't already cloned the code repository linked above, then go ahead and grab that now. </p>
<p>Once you have that, go inside the <code>hello-kube</code> directory. This directory contains the code for the <code>hello-kube</code> application as well as the <code>Dockerfile</code> for building the image.</p>
<pre><code class="lang-bash">├── Dockerfile
├── index.html
├── package.json
├── public
└── src

2 directories, 3 files
</code></pre>
<p>The JavaScript code lives inside the <code>src</code> folder but that's not of interest to you. The file you should be looking at is the <code>Dockerfile</code> because it can give you insight into how you should plan your deployment. The contents of the <code>Dockerfile</code> are as follows:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node as builder

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /usr/app</span>

<span class="hljs-keyword">COPY</span><span class="bash"> ./package.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>

<span class="hljs-keyword">FROM</span> nginx
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /usr/app/dist /usr/share/nginx/html</span>
</code></pre>
<p>As you can see, this is a <a target="_blank" href="https://www.freecodecamp.org/news/the-docker-handbook/#multi-staged-builds">multi-staged build</a> process.</p>
<ul>
<li>The first stage uses <code>node</code> as the base image and compiles the JavaScript application into a bunch of production ready files.</li>
<li>The second stage copies the files built during the first stage, and pastes them inside the default NGINX document root. Given that the base image for the second phase is <code>nginx</code>, the resulting image will be an <code>nginx</code> image serving the files built during the first phase on port 80 (default port for nginx).</li>
</ul>
<p>Now to deploy this application on Kubernetes, you'll have to find a way to run the image as a container and make port 80 accessible from the outside world.</p>
<h3 id="heading-writing-your-first-set-of-configurations">Writing Your First Set of Configurations</h3>
<p>In the declarative approach, instead of issuing individual commands in the terminal, you instead write down the necessary configuration in a YAML file and feed that to Kubernetes. </p>
<p>In the <code>hello-kube</code> project directory, create another directory named <code>k8s</code>. <code>k8s</code> is short for k(ubernete = 8 character)s. </p>
<p>You don't need to name the folder this way, you can name it whatever you want. </p>
<p>It's not even necessary to keep it within the project directory. These configuration files can live anywhere in your computer, as they have no relation to the project source code.</p>
<p>Now inside that <code>k8s</code> directory, create a new file named <code>hello-kube-pod.yaml</code>. I will go ahead and write the code for the file first and then I'll go line by line and explain it to you. The content for this file is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-kube-pod</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">component:</span> <span class="hljs-string">web</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">hello-kube</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/hello-kube</span>
      <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<p>Every valid Kubernetes configuration file has four required fields. They are as follows:</p>
<ul>
<li><code>apiVersion</code>: Which version of the Kubernetes API you're using to create this object. This value may change depending on the kind of object you are creating. For creating a <code>Pod</code> the required version is <code>v1</code>.</li>
<li><code>kind</code>: What kind of object you want to create. Objects in Kubernetes can be of many kinds. As you go through the article, you'll learn about a lot of them, but for now, just understand that you're creating a <code>Pod</code> object.</li>
<li><code>metadata</code>: Data that helps uniquely identify the object. Under this field you can have information like <code>name</code>, <code>labels</code>, <code>annotation</code> etc. The <code>metadata.name</code> string will show up on the terminal and will be used in <code>kubectl</code> commands. The key value pair under the <code>metadata.labels</code> field doesn't have to be <code>components: web</code>. You can give it any label like <code>app: hello-kube</code>. This value will be used as the selector when creating the <code>LoadBalancer</code> service very soon.</li>
<li><code>spec</code>: contains the state you desire for the object. The <code>spec.containers</code> sub-field contains information about the containers that will run inside this <code>Pod</code>. The <code>spec.containers.name</code> value is what the container runtime inside the node will assign to the newly created container. The <code>spec.containers.image</code> is the container image to be used for creating this container. And the <code>spec.containers.ports</code> field holds configuration regarding various ports configuration. <code>containerPort: 80</code> indicates that you want to expose port 80 from the container.</li>
</ul>
<p>If you're on a Raspberry Pi, use <code>raed667/hello-kube</code> as image instead of <code>fhsinchy/hello-kube</code>. Now to feed this configuration file to Kubernetes, you'll use the <code>apply</code> command. Generic syntax for the command is as follows:</p>
<pre><code>kubectl apply -f &lt;configuration file&gt;
</code></pre><p>To feed a configuration file named <code>hello-kube-pod.yaml</code>, the command will be as follows:</p>
<pre><code class="lang-bash">kubectl apply -f hello-kube-pod.yaml

<span class="hljs-comment"># pod/hello-kube-pod created</span>
</code></pre>
<p>To make sure that the <code>Pod</code> is up and running, execute the following command:</p>
<pre><code class="lang-bash">kubectl get pod

<span class="hljs-comment"># NAME         READY   STATUS    RESTARTS   AGE</span>
<span class="hljs-comment"># hello-kube   1/1     Running   0          3m3s</span>
</code></pre>
<p>You should see <code>Running</code> in the <code>STATUS</code> column. If you see something like <code>ContainerCreating</code> wait for a minute or two and check again.</p>
<p>Once the <code>Pod</code> is up and running, it's time for you to write the configuration file for the <code>LoadBalancer</code> service. </p>
<p>Create another file inside the <code>k8s</code> directory called <code>hello-kube-load-balancer-service.yaml</code> and put following code in it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-kube-load-balancer-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">80</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">component:</span> <span class="hljs-string">web</span>
</code></pre>
<p>Like the previous configuration file, <code>apiVersion</code>, <code>kind</code>, and <code>metadata</code> fields serve the same purpose here. As you can see there are no <code>labels</code> field inside <code>metadata</code> here. That's because a service selects other objects using <code>labels</code>, other objects don't select a service.</p>
<blockquote>
<p>Remember, services set-up an access policy for other objects, other objects don't set-up an access policy for a service.</p>
</blockquote>
<p>Inside the <code>spec</code> field you can see a new set of values. Unlike a <code>Pod</code>, services have four types. These are <code>ClusterIP</code>, <code>NodePort</code>, <code>LoadBalancer</code>, and <code>ExternalName</code>.</p>
<p>In this example, you're using the type <code>LoadBalancer</code>, which is the standard way for exposing a service outside the cluster. This service will give you an IP address that you can then use to connect to the applications running inside your cluster.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/load-balancer-4.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The <code>LoadBalancer</code> type requires two port values to work properly. Under the <code>ports</code> field, the <code>port</code> value is for accessing the pod itself and its value can be anything you want. </p>
<p>The <code>targetPort</code> value is the one from inside the container and has to match up with the port that you want to expose from inside the container. </p>
<p>I've already said that the <code>hello-kube</code> application runs on port 80 inside the container . You've even exposed this port in the <code>Pod</code> configuration file, so the <code>targetPort</code> will be <code>80</code>.</p>
<p>The <code>selector</code> field is used to identify the objects that will be connected to this service. The <code>component: web</code> key-value pair has to match up with the key-value pair under the <code>labels</code> field in the <code>Pod</code> configuration file. If you've used some other key value pair like <code>app: hello-kube</code> in that configuration file, use that instead.</p>
<p>To feed this file to Kubernetes you will again use the <code>apply</code> command. The command for feeding a file named <code>hello-kube-load-balancer-service.yaml</code> will be as follows:</p>
<pre><code class="lang-bash">kubectl apply -f hello-kube-load-balancer-service.yaml

<span class="hljs-comment"># service/hello-kube-load-balancer-service created</span>
</code></pre>
<p>To make sure the load balancer has been created successfully execute the following command:</p>
<pre><code class="lang-bash">kubectl get service

<span class="hljs-comment"># NAME                               TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE</span>
<span class="hljs-comment"># hello-kube-load-balancer-service   LoadBalancer   10.107.231.120   &lt;pending&gt;     80:30848/TCP   7s</span>
<span class="hljs-comment"># kubernetes                         ClusterIP      10.96.0.1        &lt;none&gt;        443/TCP        21h</span>
</code></pre>
<p>Make sure you see the <code>hello-kube-load-balancer-service</code> name in the list. Now that you have a pod running that is exposed, you can go ahead and access that. Execute the following command to do so:</p>
<pre><code class="lang-bash">minikube service hello-kube-load-balancer-service

<span class="hljs-comment"># |-----------|----------------------------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># | NAMESPACE |           NAME                   | TARGET PORT |             URL             |</span>
<span class="hljs-comment"># |-----------|----------------------------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># | default   | hello-kube-load-balancer-service |          80 | http://192.168.99.101:30848 |</span>
<span class="hljs-comment"># |-----------|----------------------------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># ?  Opening service default/hello-kube-load-balancer in default browser...</span>
</code></pre>
<p>Your default web browser should open automatically and you should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-87.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can also feed both files together instead of feeding them individually. To do that you can replace the file name with the directory name as follows:</p>
<pre><code class="lang-bash">kubectl apply -f k8s

<span class="hljs-comment"># service/hello-kube-load-balancer-service created</span>
<span class="hljs-comment"># pod/hello-kube-pod created</span>
</code></pre>
<p>In this case make sure your terminal is on the parent directory of the <code>k8s</code> directory. </p>
<p>If you're inside the <code>k8s</code> directory, you can use a dot (<code>.</code>) to refer to the current directory. When mass applying configurations, it can be a good idea to get rid of resources created previously. That way the possibility of conflicts becomes much lower.</p>
<p>The declarative approach is the ideal one when working with Kubernetes. Except for some special cases, that you'll see near the end of the article.</p>
<h3 id="heading-the-kubernetes-dashboard">The Kubernetes Dashboard</h3>
<p>In a previous section, you used the <code>delete</code> command to get rid of a Kubernetes object. </p>
<p>In this section, however, I thought introducing the dashboard would be great idea. The Kubernetes Dashboard is a graphical UI that you can use to manage your workloads, services, and more.</p>
<p>To launch the Kubernetes Dashboard, execute the following command in your terminal:</p>
<pre><code class="lang-bash">minikube dashboard

<span class="hljs-comment"># ? Verifying dashboard health ...</span>
<span class="hljs-comment"># ? Launching proxy ...</span>
<span class="hljs-comment"># ? Verifying proxy health ...</span>
<span class="hljs-comment"># ? Opening http://127.0.0.1:52393/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser...</span>
</code></pre>
<p>The dashboard should open automatically in your default browser:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-88.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The UI is pretty user-friendly and you are free to roam around here. Although it's completely possible to create, manage, and delete objects from this UI, I'll be using the CLI for the rest of this article.</p>
<p>Here in the <em>Pods</em> list, you can use the three dots menu on the right side to <em>Delete</em> the Pod. You can do the same with the <code>LoadBalancer</code> service as well. In fact the <em>Services</em> list is conveniently placed right after the <em>Pods</em> list. </p>
<p>You can close the dashboard by hitting the <code>Ctrl + C</code> key combination or closing the terminal window.</p>
<h2 id="heading-working-with-multi-container-applications">Working with Multi-Container Applications</h2>
<p>So far you've worked with applications that run within a single container. </p>
<p>In this section, you'll be working with an application consisting of two containers. You'll also get familiar with <code>Deployment</code>, <code>ClusterIP</code>, <code>PersistentVolume</code>, <code>PersistentVolumeClaim</code> and some debugging techniques.</p>
<p>The application you'll be working with is a simple express notes API with full CRUD functionality. The application uses <a target="_blank" href="https://www.postgresql.org/">PostgreSQL</a> as its database system. So you're not only going to deploy the application but also set-up internal networking between the application and the database.</p>
<p>The code for the application is inside the <code>notes-api</code> directory inside the project repo.</p>
<pre><code>.
├── api
├── docker-compose.yaml
└── postgres

<span class="hljs-number">2</span> directories, <span class="hljs-number">1</span> file
</code></pre><p>The application source code resides inside the <code>api</code> directory and the <code>postgres</code> directory contains a <code>Dockerfile</code> for creating the custom <code>postgres</code> image. The <code>docker-compose.yaml</code> file contains the necessary configuration for running the application using <code>docker-compose</code>.</p>
<p>Just like with the previous project, you can look into the individual <code>Dockerfile</code> for each service to get a sense of how the application runs inside the container. </p>
<p>Or you can just inspect the <code>docker-compose.yaml</code> and plan your Kubernetes deployment using that.</p>
<pre><code class="lang-yaml">
<span class="hljs-attr">version:</span> <span class="hljs-string">"3.8"</span>

<span class="hljs-attr">services:</span> 
    <span class="hljs-attr">db:</span>
        <span class="hljs-attr">build:</span>
            <span class="hljs-attr">context:</span> <span class="hljs-string">./postgres</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">db-data:/var/lib/postgresql/data</span>
        <span class="hljs-attr">environment:</span>
            <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">63eaQB9wtLqmNBpg</span>
            <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">notesdb</span>
    <span class="hljs-attr">api:</span>
        <span class="hljs-attr">build:</span> 
            <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">ports:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-number">3000</span><span class="hljs-string">:3000</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./api:/home/node/app</span>
        <span class="hljs-attr">environment:</span> 
            <span class="hljs-attr">DB_CONNECTION:</span> <span class="hljs-string">pg</span>
            <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">db</span>
            <span class="hljs-attr">DB_PORT:</span> <span class="hljs-number">5432</span>
            <span class="hljs-attr">DB_USER:</span> <span class="hljs-string">postgres</span>
            <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">DB_PASSWORD:</span> <span class="hljs-string">63eaQB9wtLqmNBpg</span>

<span class="hljs-attr">volumes:</span>
    <span class="hljs-attr">db-data:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">notes-db-dev-data</span>
</code></pre>
<p>Looking at the <code>api</code> service definition, you can see that the application runs on port 3000 inside the container. It also requires a bunch of environment variables to function properly. </p>
<p>The volumes can be ignored as they were necessary for development purposes only and the build configuration is Docker-specific. So the two sets of information that you can carry over to your Kubernetes configuration files almost unchanged are as follows:</p>
<ul>
<li>Port mappings – because you'll have to expose the same port from the container.</li>
<li>Environment variables – because these variables are going to be the same across all environments (the values are going to change, though).</li>
</ul>
<p>The <code>db</code> service is even simpler. All it has is bunch of environment variables. You can even use the official <code>postgres</code> image instead of a custom one. </p>
<p>But the only reason for a custom image is if you want the database instance to come with the <code>notes</code> table pre-created. </p>
<p>This table is necessary for the application. If you look inside the <code>postgres/docker-entrypoint-initdb.d</code> directory, you'll see a file named <code>notes.sql</code> which is used for creating the database during initialization.</p>
<h3 id="heading-deployment-plan">Deployment Plan</h3>
<p>Unlike the previous project you deployed, this project is going to be a bit more complicated. </p>
<p>In this project, you'll create not one but three instances of the notes API. These three instances will be exposed outside of the cluster using a <code>LoadBalancer</code> service.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/notes-api-1.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Apart from these three instances, there will be another instance of the PostgreSQL database system. All three instances of the notes API application will communicate with this database instance using a <code>ClusterIP</code> service.</p>
<p><code>ClusterIP</code> service is another type of Kubernetes service that exposes an application within your cluster. That means no outside traffic can reach the application using a <code>ClusterIP</code> service.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/cluster-ip-2.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In this project, the database has to be accessed by the notes API only, so exposing the database service within the cluster is an ideal choice.</p>
<p>I've already mentioned in a previous section that you shouldn't create pods directly. So in this project, you'll be using a <code>Deployment</code> instead of a <code>Pod</code>.</p>
<h3 id="heading-replication-controllers-replica-sets-and-deployments">Replication Controllers, Replica Sets, and Deployments</h3>
<p>According to the Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/architecture/controller/">documentation</a> - </p>
<blockquote>
<p>"In Kubernetes, controllers are control loops that watch the state of your cluster, then make or request changes where needed. Each controller tries to move the current cluster state closer to the desired state. A control loop is a non-terminating loop that regulates the state of a system."</p>
</blockquote>
<p>A <code>ReplicationController</code>, as the name suggests allows you to easily create multiple replicas very easily. Once the desired number of replicas are created, the controller will make sure that the state stays that way.</p>
<p>If after some time you decide to lower the number of replicas, then the <code>ReplicationController</code> will take actions immediately and get rid of the extra pods. </p>
<p>Otherwise if the number of replicas becomes lower than what you wanted (maybe some of the pods have crashed) the <code>ReplicationController</code> will create new ones to match the desired state.</p>
<p>As useful as they may sound to you, the <code>ReplicationController</code> is not the recommended way of creating replicas nowadays. A newer API called a <code>ReplicaSet</code> has taken the place.  </p>
<p>Apart from the fact that a <code>ReplicaSet</code> can provide you with a wider range of selection option, both <code>ReplicationController</code> and <code>ReplicaSet</code> are more or less the same thing.</p>
<p>Having a wider range of selector options is good but what's even better is having more flexibility in terms of rolling out and rolling back updates. This is where another Kubernetes API called a <code>Deployment</code> comes in.</p>
<p>A <code>Deployment</code> is like an extension to the already nice <code>ReplicaSet</code> API. <code>Deployment</code> not only allows you to create replicas in no time, but also allows you to release updates or go back to a previous function with just one or two <code>kubectl</code> commands.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>ReplicationController</td><td>ReplicaSet</td><td>Deployment</td></tr>
</thead>
<tbody>
<tr>
<td>Allows the creation of multiple pods easily</td><td>Allows the creation of multiple pods easily</td><td>Allows the creation of multiple pods easily</td></tr>
<tr>
<td>The original method of replication in Kubernetes</td><td>Has more flexible selectors</td><td>Extends ReplicaSets with easy update roll-out and roll-back</td></tr>
</tbody>
</table>
</div><p>In this project, you'll be using a <code>Deployment</code> to maintain the application instances.</p>
<h3 id="heading-creating-your-first-deployment">Creating Your First Deployment</h3>
<p>Let's begin by writing the configuration file for the notes API deployment. Create a <code>k8s</code> directory inside the <code>notes-api</code> project directory. </p>
<p>Inside that directory, create a file named <code>api-deployment.yaml</code> and put following content in it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-api</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">3000</span>
</code></pre>
<p>In this file, the <code>apiVersion</code>, <code>kind</code>, <code>metadata</code> and <code>spec</code> fields serve the same purpose as the previous project. Notable changes in this file from the last one are as follows:</p>
<ul>
<li>For creating a Pod, the required <code>apiVersion</code> was <code>v1</code>. But for creating a Deployment, the required version is <code>apps/v1</code>. Kubernetes API versions can be a bit confusing at times, but as you keep working with Kubernetes you'll get the hang of them. Also you can consult the official <a target="_blank" href="https://kubernetes.io/docs/home/">docs</a> for example YAML files to refer to. The kind is <code>Deployment</code> which is pretty self-explanatory.</li>
<li><code>spec.replicas</code> defines the number of running replicas. Setting this value to <code>3</code> means you let Kubernetes know that you want three instances of your application running at all times.</li>
<li><code>spec.selector</code> is where you let the <code>Deployment</code> know which pods to control. I've already mentioned that a <code>Deployment</code> is an extension to <code>ReplicaSet</code> and can control a set of Kubernetes objects. Setting <code>selector.matchLabels</code> to <code>component: api</code> means this <code>Deployment</code> will control the pods that have a label of <code>component: api</code>. This line is letting Kubernetes know that you want this <code>Deployment</code> to control all the pods having the <code>component: api</code> label.</li>
<li><code>spec.template</code> is the template for configuring the pods. It's almost the same as the previous configuration file.</li>
</ul>
<p>If you're on a Raspberry Pi, use <code>raed667/notes-api</code> instead of <code>fhsinchy/notes-api</code> as image. Now to see this configuration in action, apply the file just like in the previous project:</p>
<pre><code class="lang-bash">kubectl apply -f api-deployment.yaml

<span class="hljs-comment"># deployment.apps/api-deployment created</span>
</code></pre>
<p>To make sure the <code>Deployment</code> has been created, execute the following command:</p>
<pre><code class="lang-bash">kubectl get deployment

<span class="hljs-comment"># NAME             READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="hljs-comment"># api-deployment   0/3     3            0           2m7s</span>
</code></pre>
<p>If you look at the <code>READY</code> column, you'll see <code>0/3</code>. This means the pods have not been created yet. Wait a few minutes and try once again.</p>
<pre><code class="lang-bash">kubectl get deployment

<span class="hljs-comment"># NAME             READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="hljs-comment"># api-deployment   0/3     3            0           28m</span>
</code></pre>
<p>As you can see, I have waited nearly half an hour and still none of the pods are ready. The API itself is only a few hundred kilobytes. A deployment of this size shouldn't have taken this long. Which means there is a problem and we have to fix that.</p>
<h3 id="heading-inspecting-kubernetes-resources">Inspecting Kubernetes Resources</h3>
<p>Before you can solve a problem, you have to first find out the origin. A good starting point is the <code>get</code> command.</p>
<p>You already know the <code>get</code> command that prints a table containing important information about one or more Kubernetes resources. Generic syntax of the command is as follows:</p>
<pre><code>kubectl get &lt;resource type&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">resource</span> <span class="hljs-attr">name</span>&gt;</span></span>
</code></pre><p>To run the <code>get</code> command on your <code>api-deployment</code>, execute the following line of code in your terminal:</p>
<pre><code class="lang-bash">kubectl get deployment api-deployment

<span class="hljs-comment"># NAME             READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="hljs-comment"># api-deployment   0/3     3            0           15m</span>
</code></pre>
<p>You can omit the <code>api-deployment</code> name to get a list of all available deployments. You can also run the <code>get</code> command on a configuration file. </p>
<p>If you would like to get information about the deployments described in the <code>api-deployment.yaml</code> file, the command should be as follows:</p>
<pre><code class="lang-bash">kubectl get -f api-deployment

<span class="hljs-comment"># NAME             READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="hljs-comment"># api-deployment   0/3     3            0           18m</span>
</code></pre>
<p>By default, the <code>get</code> command shows a very small amount of information. You can get more out of it by using the <code>-o</code> option. </p>
<p>The <code>-o</code> option sets the output format for the <code>get</code> command. You can use the <code>wide</code> output format to see more details.</p>
<pre><code class="lang-bash">kubectl get -f api-deployment.yaml

<span class="hljs-comment"># NAME             READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES               SELECTOR</span>
<span class="hljs-comment"># api-deployment   0/3     3            0           19m   api          fhsinchy/notes-api   component=api</span>
</code></pre>
<p>As you can see, now the list contains more information than before. You can learn about the options for the <code>get</code> command from the official <a target="_blank" href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#get">docs</a>.</p>
<p>Running <code>get</code> on the <code>Deployment</code> doesn't spit out anything interesting, to be honest. In such cases, you have to get down to the lower level resources. </p>
<p>Have a look at the pods list and see if you can find something interesting there:</p>
<pre><code class="lang-bash">kubectl get pod

<span class="hljs-comment"># NAME                             READY   STATUS             RESTARTS   AGE</span>
<span class="hljs-comment"># api-deployment-d59f9c884-88j45   0/1     CrashLoopBackOff   10         30m</span>
<span class="hljs-comment"># api-deployment-d59f9c884-96hfr   0/1     CrashLoopBackOff   10         30m</span>
<span class="hljs-comment"># api-deployment-d59f9c884-pzdxg   0/1     CrashLoopBackOff   10         30m</span>
</code></pre>
<p>Now this is interesting. All the pods have a <code>STATUS</code> of <code>CrashLoopBackOff</code> which is new. Previously you've only seen <code>ContainerCreating</code> and <code>Running</code> statuses. You may see <code>Error</code> in place of <code>CrashLoopBackOff</code> as well. </p>
<p>Looking at the <code>RESTARTS</code> column you can see that the pods have been restarted 10 times already. This means for some reason the pods are failing to startup.</p>
<p>Now to get a more detailed look at one of the pods, you can use another command called <code>describe</code>. It's a lot like the <code>get</code> command. Generic syntax of the command is as follows:</p>
<pre><code>kubectl get &lt;resource type&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">resource</span> <span class="hljs-attr">name</span>&gt;</span></span>
</code></pre><p>To get details of the <code>api-deployment-d59f9c884-88j45</code> pod, you can execute the following command:</p>
<pre><code class="lang-bash">kubectl describe pod api-deployment-d59f9c884-88j45

<span class="hljs-comment"># Name:         api-deployment-d59f9c884-88j45</span>
<span class="hljs-comment"># Namespace:    default</span>
<span class="hljs-comment"># Priority:     0</span>
<span class="hljs-comment"># Node:         minikube/172.28.80.217</span>
<span class="hljs-comment"># Start Time:   Sun, 09 Aug 2020 16:01:28 +0600</span>
<span class="hljs-comment"># Labels:       component=api</span>
<span class="hljs-comment">#               pod-template-hash=d59f9c884</span>
<span class="hljs-comment"># Annotations:  &lt;none&gt;</span>
<span class="hljs-comment"># Status:       Running</span>
<span class="hljs-comment"># IP:           172.17.0.4</span>
<span class="hljs-comment"># IPs:</span>
<span class="hljs-comment">#   IP:           172.17.0.4</span>
<span class="hljs-comment"># Controlled By:  ReplicaSet/api-deployment-d59f9c884</span>
<span class="hljs-comment"># Containers:</span>
<span class="hljs-comment">#  api:</span>
<span class="hljs-comment">#     Container ID:   docker://d2bc15bda9bf4e6d08f7ca8ff5d3c8593655f5f398cf8bdd18b71da8807930c1</span>
<span class="hljs-comment">#     Image:          fhsinchy/notes-api</span>
<span class="hljs-comment">#     Image ID:       docker-pullable://fhsinchy/notes-api@sha256:4c715c7ce3ad3693c002fad5e7e7b70d5c20794a15dbfa27945376af3f3bb78c</span>
<span class="hljs-comment">#     Port:           3000/TCP</span>
<span class="hljs-comment">#     Host Port:      0/TCP</span>
<span class="hljs-comment">#     State:          Waiting</span>
<span class="hljs-comment">#       Reason:       CrashLoopBackOff</span>
<span class="hljs-comment">#     Last State:     Terminated</span>
<span class="hljs-comment">#       Reason:       Error</span>
<span class="hljs-comment">#       Exit Code:    1</span>
<span class="hljs-comment">#       Started:      Sun, 09 Aug 2020 16:13:12 +0600</span>
<span class="hljs-comment">#       Finished:     Sun, 09 Aug 2020 16:13:12 +0600</span>
<span class="hljs-comment">#     Ready:          False</span>
<span class="hljs-comment">#     Restart Count:  10</span>
<span class="hljs-comment">#     Environment:    &lt;none&gt;</span>
<span class="hljs-comment">#     Mounts:</span>
<span class="hljs-comment">#      /var/run/secrets/kubernetes.io/serviceaccount from default-token-gqfr4 (ro)</span>
<span class="hljs-comment"># Conditions:</span>
<span class="hljs-comment">#   Type              Status</span>
<span class="hljs-comment">#   Initialized       True</span>
<span class="hljs-comment">#   Ready             False</span>
<span class="hljs-comment">#   ContainersReady   False</span>
<span class="hljs-comment">#   PodScheduled      True</span>
<span class="hljs-comment"># Volumes:</span>
<span class="hljs-comment">#   default-token-gqfr4:</span>
<span class="hljs-comment">#     Type:        Secret (a volume populated by a Secret)</span>
<span class="hljs-comment">#     SecretName:  default-token-gqfr4</span>
<span class="hljs-comment">#     Optional:    false</span>
<span class="hljs-comment"># QoS Class:       BestEffort</span>
<span class="hljs-comment"># Node-Selectors:  &lt;none&gt;</span>
<span class="hljs-comment"># Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s</span>
<span class="hljs-comment">#                  node.kubernetes.io/unreachable:NoExecute for 300s</span>
<span class="hljs-comment"># Events:</span>
<span class="hljs-comment">#   Type     Reason     Age                         From               Message</span>
<span class="hljs-comment">#   ----     ------     ----                        ----               -------</span>
<span class="hljs-comment">#   Normal   Scheduled  &lt;unknown&gt;                   default-scheduler  Successfully assigned default/api-deployment-d59f9c884-88j45 to minikube</span>
<span class="hljs-comment">#   Normal   Pulled     2m40s (x4 over 3m47s)       kubelet, minikube  Successfully pulled image "fhsinchy/notes-api"</span>
<span class="hljs-comment">#   Normal   Created    2m40s (x4 over 3m47s)       kubelet, minikube  Created container api</span>
<span class="hljs-comment">#   Normal   Started    2m40s (x4 over 3m47s)       kubelet, minikube  Started container api</span>
<span class="hljs-comment">#   Normal   Pulling    107s (x5 over 3m56s)        kubelet, minikube  Pulling image "fhsinchy/notes-api"</span>
<span class="hljs-comment">#   Warning  BackOff    &lt;invalid&gt; (x44 over 3m32s)  kubelet, minikube  Back-off restarting failed container</span>
</code></pre>
<p>The most interesting part in this entire wall of text is the <code>Events</code> section. Have a closer look:</p>
<pre><code>Events:
  Type     Reason     Age                         From               Message
  ----     ------     ----                        ----               -------
  Normal   Scheduled  &lt;unknown&gt;                   <span class="hljs-keyword">default</span>-scheduler  Successfully assigned <span class="hljs-keyword">default</span>/api-deployment-d59f9c884<span class="hljs-number">-88</span>j45 to minikube
  Normal   Pulled     <span class="hljs-number">2</span>m40s (x4 over <span class="hljs-number">3</span>m47s)       kubelet, minikube  Successfully pulled image <span class="hljs-string">"fhsinchy/notes-api"</span>
  Normal   Created    <span class="hljs-number">2</span>m40s (x4 over <span class="hljs-number">3</span>m47s)       kubelet, minikube  Created container api
  Normal   Started    <span class="hljs-number">2</span>m40s (x4 over <span class="hljs-number">3</span>m47s)       kubelet, minikube  Started container api
  Normal   Pulling    <span class="hljs-number">107</span>s (x5 over <span class="hljs-number">3</span>m56s)        kubelet, minikube  Pulling image <span class="hljs-string">"fhsinchy/notes-api"</span>
  Warning  BackOff    &lt;invalid&gt; (x44 over <span class="hljs-number">3</span>m32s)  kubelet, minikube  Back-off restarting failed container
</code></pre><p>From these events, you can see that the container image was pulled successfully. The container was created as well, but it's evident from the <code>Back-off restarting failed container</code> that the container failed to startup.</p>
<p>The describe command is very similar to the <code>get</code> command and has the same sort of options. </p>
<p>You can omit the <code>api-deployment-d59f9c884-88j45</code> name to get information about all available pods. Or you can also use the <code>-f</code> option to pass a configuration file to the command. Visit the official <a target="_blank" href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#describe">docs</a> to learn more.</p>
<p>Now that you know that there is something wrong with the container, you have to go down to the container level and see what's going on there.</p>
<h3 id="heading-getting-container-logs-from-pods">Getting Container Logs from Pods</h3>
<p>There is another <code>kubectl</code> command called <code>logs</code> that can help you to get the container logs from inside a pod. Generic syntax for the command is as follows:</p>
<pre><code>kubectl logs &lt;pod&gt;
</code></pre><p>To view the logs inside the <code>api-deployment-d59f9c884-88j45</code> pod, the command should be as follows:</p>
<pre><code class="lang-bash">kubectl logs api-deployment-d59f9c884-88j45

<span class="hljs-comment"># &gt; api@1.0.0 start /usr/app</span>
<span class="hljs-comment"># &gt; cross-env NODE_ENV=production node bin/www</span>

<span class="hljs-comment"># /usr/app/node_modules/knex/lib/client.js:55</span>
<span class="hljs-comment">#     throw new Error(`knex: Required configuration option 'client' is missing.`);</span>
    ^

<span class="hljs-comment"># Error: knex: Required configuration option 'client' is missing.</span>
<span class="hljs-comment">#     at new Client (/usr/app/node_modules/knex/lib/client.js:55:11)</span>
<span class="hljs-comment">#     at Knex (/usr/app/node_modules/knex/lib/knex.js:53:28)</span>
<span class="hljs-comment">#     at Object.&lt;anonymous&gt; (/usr/app/services/knex.js:5:18)</span>
<span class="hljs-comment">#     at Module._compile (internal/modules/cjs/loader.js:1138:30)</span>
<span class="hljs-comment">#     at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)</span>
<span class="hljs-comment">#     at Module.load (internal/modules/cjs/loader.js:986:32)</span>
<span class="hljs-comment">#     at Function.Module._load (internal/modules/cjs/loader.js:879:14)</span>
<span class="hljs-comment">#     at Module.require (internal/modules/cjs/loader.js:1026:19)</span>
<span class="hljs-comment">#     at require (internal/modules/cjs/helpers.js:72:18)</span>
<span class="hljs-comment">#     at Object.&lt;anonymous&gt; (/usr/app/services/index.js:1:14)</span>
<span class="hljs-comment"># npm ERR! code ELIFECYCLE</span>
<span class="hljs-comment"># npm ERR! errno 1</span>
<span class="hljs-comment"># npm ERR! api@1.0.0 start: `cross-env NODE_ENV=production node bin/www`</span>
<span class="hljs-comment"># npm ERR! Exit status 1</span>
<span class="hljs-comment"># npm ERR!</span>
<span class="hljs-comment"># npm ERR! Failed at the api@1.0.0 start script.</span>
<span class="hljs-comment"># npm ERR! This is probably not a problem with npm. There is likely additional logging output above.</span>

<span class="hljs-comment"># npm ERR! A complete log of this run can be found in:</span>
<span class="hljs-comment"># npm ERR!     /root/.npm/_logs/2020-08-09T10_28_52_779Z-debug.log</span>
</code></pre>
<p>Now this is what you need to debug the problem. Looks like the <a target="_blank" href="http://knexjs.org/">knex.js</a> library is missing a required value, which is preventing the application from starting. You can learn more about the <code>logs</code> command from the official <a target="_blank" href="https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#logs">docs</a>.</p>
<p>This is happening because you're missing some required environment variables in the deployment definition. </p>
<p>If you take another look at the <code>api</code> service definition inside the <code>docker-compose.yaml</code> file, you should see something like this:</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">api:</span>
        <span class="hljs-attr">build:</span> 
            <span class="hljs-attr">context:</span> <span class="hljs-string">./api</span>
            <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.dev</span>
        <span class="hljs-attr">ports:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-number">3000</span><span class="hljs-string">:3000</span>
        <span class="hljs-attr">volumes:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-string">/home/node/app/node_modules</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">./api:/home/node/app</span>
        <span class="hljs-attr">environment:</span> 
            <span class="hljs-attr">DB_CONNECTION:</span> <span class="hljs-string">pg</span>
            <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">db</span>
            <span class="hljs-attr">DB_PORT:</span> <span class="hljs-number">5432</span>
            <span class="hljs-attr">DB_USER:</span> <span class="hljs-string">postgres</span>
            <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-attr">DB_PASSWORD:</span> <span class="hljs-string">63eaQB9wtLqmNBpg</span>
</code></pre>
<p>These environment variables are required for the application to communicate with the database. So adding these to the deployment configuration should fix the issue.</p>
<h3 id="heading-environment-variables">Environment Variables</h3>
<p>Adding environment variables to a Kubernetes configuration file is very straightforward. Open up the <code>api-deployment.yaml</code> file and update its content to look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-api</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">3000</span>

          <span class="hljs-comment"># these are the environment variables</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_CONNECTION</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">pg</span>
</code></pre>
<p>The <code>containers.env</code> field contains all the environment variables. If you look closely, you'll see that I haven't added all the environment variables from the <code>docker-compose.yaml</code> file. I have added only one. </p>
<p>The <code>DB_CONNECTION</code> indicates that the application is using a PostgreSQL database. Adding this single variable should fix the problem.</p>
<p>Now apply the configuration file again by executing the following command:</p>
<pre><code class="lang-bash">kubectl apply -f api-deployment.yaml

<span class="hljs-comment"># deployment.apps/api-deployment configured</span>
</code></pre>
<p>Unlike the other times, the output here says that a resource has been <code>configured</code>. This is the beauty of Kubernetes. You can just fix issues and re-apply the same configuration file immediately.</p>
<p>Now use the <code>get</code> command once more to make sure everything is running properly.</p>
<pre><code class="lang-bash">kubectl get deployment

<span class="hljs-comment"># NAME             READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="hljs-comment"># api-deployment   3/3     3            3           68m</span>

kubectl get pod

<span class="hljs-comment"># NAME                              READY   STATUS    RESTARTS   AGE</span>
<span class="hljs-comment"># api-deployment-66cdd98546-l9x8q   1/1     Running   0          7m26s</span>
<span class="hljs-comment"># api-deployment-66cdd98546-mbfw9   1/1     Running   0          7m31s</span>
<span class="hljs-comment"># api-deployment-66cdd98546-pntxv   1/1     Running   0          7m21s</span>
</code></pre>
<p>All three pods are running and the <code>Deployment</code> is running fine as well.</p>
<h3 id="heading-creating-the-database-deployment">Creating the Database Deployment</h3>
<p>Now that the API is up and running, it's time to write the configuration for the database instance. </p>
<p>Create another file called <code>postgres-deployment.yaml</code> inside the <code>k8s</code> directory and put the following content in it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-postgres</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">5432</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">63eaQB9wtLqmNBpg</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_DB</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">notesdb</span>
</code></pre>
<p>If you're on a Raspberry Pi, use <code>raed667/notes-postgres</code> instead of <code>fhsinchy/notes-postgres</code> as image. The configuration itself is very similar to the previous one. I am not going to explain everything in this file – hopefully you understand it by yourself with the knowledge you've gained from this article so far.</p>
<p>PostgreSQL runs on port 5432 by default, and the <code>POSTGRES_PASSWORD</code> variable is required for running the <code>postgres</code> container. This password will also be used for connecting to this database by the API. </p>
<p>The <code>POSTGRES_DB</code> variable is optional. But because of the way this project has been structured, it's necessary here – otherwise the initialization will fail. </p>
<p>You can learn more about the official <a target="_blank" href="https://hub.docker.com/_/postgres">postgres</a> Docker image from their Docker Hub page. For the sake of simplicity, I'm keeping the replica count to 1 in this project.</p>
<p>To apply this file, execute the following command:</p>
<pre><code class="lang-bash">kubectl apply -f postgres-deployment.yaml

<span class="hljs-comment"># deployment.apps/postgres-deployment created</span>
</code></pre>
<p>Use the <code>get</code> command to ensure that the deployment and the pods are running properly:</p>
<pre><code class="lang-bash">kubectl get deployment

<span class="hljs-comment"># NAME                  READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="hljs-comment"># postgres-deployment   1/1     1            1           13m</span>

kubectl get pod

<span class="hljs-comment"># NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="hljs-comment"># postgres-deployment-76fcc75998-mwnb7   1/1     Running   0          13m</span>
</code></pre>
<p>Although the deployment and the pods are running properly, there is a big issue with the database deployment.</p>
<p>If you've worked with any database system before, you may already know that databases store data in the filesystem. Right now the database deployment looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/postgres-1.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The <code>postgres</code> container is encapsulated by a pod. Whatever data is saved stays within the internal filesystem of the container. </p>
<p>Now, if for some reason, the container crashes or the pod encapsulating the container goes down, all data persisted inside the filesystem will be lost.</p>
<p>Upon crashing, Kubernetes will create a new pod to maintain the desired state, but there is no data carry over mechanism between the two pods whatsoever.</p>
<p>To solve this issue, you can store the data in a separate space outside the pod within the cluster.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/volume.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Managing such storage is a distinct problem from managing compute instances. The <code>PersistentVolume</code> subsystem in Kubernetes provides an API for users and administrators that abstracts details of how storage is provided from how it is consumed.</p>
<h3 id="heading-persistent-volumes-and-persistent-volume-claims">Persistent Volumes and Persistent Volume Claims</h3>
<p>According to the Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">documentation</a> — </p>
<blockquote>
<p>"A <code>PersistentVolume</code> (PV) is a piece of storage in the cluster that has been provisioned by an administrator or dynamically provisioned using a <code>StorageClass</code>. It is a resource in the cluster just like a node is a cluster resource."</p>
</blockquote>
<p>Which essentially means that a <code>PersistentVolume</code> is a way to take a slice from your storage space and reserve that for a certain pod. Volumes are always consumed by pods and not some high level object like a deployment. </p>
<p>If you want to use a volume with a deployment that has multiple pods, you'll have to go through some additional steps.</p>
<p>Create a new file called <code>database-persistent-volume.yaml</code> inside the <code>k8s</code> directory and put following content in that file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">PersistentVolume</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">database-persistent-volume</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">storageClassName:</span> <span class="hljs-string">manual</span>
  <span class="hljs-attr">capacity:</span>
    <span class="hljs-attr">storage:</span> <span class="hljs-string">5Gi</span>
  <span class="hljs-attr">accessModes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">ReadWriteOnce</span>
  <span class="hljs-attr">hostPath:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">"/mnt/data"</span>
</code></pre>
<p>The <code>apiVersion</code>, <code>kind</code>, and <code>metadata</code> serve the same purpose as any other configuration file. The <code>spec</code> field, however, contains some new fields.</p>
<ul>
<li><code>spec.storageClassName</code> indicates the class name for this volume. Assume that a cloud provider has three kinds of storage available. These can be <em>slow</em>, <em>fast</em>, and <em>very fast</em>. The kind of storage you get from the provider will depend on the amount of money you're paying. If you ask for a very fast storage, you'll have to pay more. These different types of storage are the classes. I am using <code>manual</code> as an example here. You can use whatever you like in your local cluster.</li>
<li><code>spec.capacity.storage</code> is the amount of storage this volume will have. I am giving it 5 gigabytes of storage in this project.</li>
<li><code>spec.accessModes</code> sets the access mode for the volume. There are three possible access modes. <code>ReadWriteOnce</code> means the volume can be mounted as read-write by a single node. <code>ReadWriteMany</code> on the other hand means the volume can be mounted as read-write by many nodes. <code>ReadOnlyMany</code> means the volume can be mounted read-only by many nodes.</li>
<li><code>spec.hostPath</code> is something development specific. It indicates the directory in your local single node cluster that'll be treated as persistent volume. <code>/mnt/data</code> means that the data saved in this persistent volume will live inside the <code>/mnt/data</code> directory in the cluster.</li>
</ul>
<p>To apply this file, execute the following command:</p>
<pre><code class="lang-bash">kubectl apply -f database-persistent-volume.yaml

<span class="hljs-comment"># persistentvolume/database-persistent-volume created</span>
</code></pre>
<p>Now use the <code>get</code> command to verify that the volume has been created:</p>
<pre><code class="lang-bash">kubectl get persistentvolume

<span class="hljs-comment"># NAME                         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE</span>
<span class="hljs-comment"># database-persistent-volume   5Gi        RWO            Retain           Available           manual                  58s</span>
</code></pre>
<p>Now that the persistent volume has been created, you need a way to let the postgres pod access it. This is where a <code>PersistentVolumeClaim</code> (PVC) comes in. </p>
<p>A persistent volume claim is a request for storage by a pod. Assume that in a cluster, you have quite a lot of volumes. This claim will define the characteristics that a volume must meet to be able to satisfy a pods' necessities.</p>
<p>A real-life example can be you buying an SSD from a store. You go to the store and the salesperson shows you the following models:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Model 1</td><td>Model 2</td><td>Model 3</td></tr>
</thead>
<tbody>
<tr>
<td>128GB</td><td>256GB</td><td>512GB</td></tr>
<tr>
<td>SATA</td><td>NVME</td><td>SATA</td></tr>
</tbody>
</table>
</div><p>Now, you claim for a model that has at least 200GB of storage capacity and is an NVME drive. </p>
<p>The first one has less than 200GB and is SATA, so it doesn't match your claim. The third one has more than 200GB, but is not NVME. The second one however has more than 200GB and is also an NVME. So that's the one you get.</p>
<p>The SSD models that the salesperson showed you are equivalent to persistent volumes and your requirements are equivalent to persistent volume claims.</p>
<p>Create another new file called <code>database-persistent-volume-claim.yaml</code> inside the <code>k8s</code> directory and put the following content in that file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">PersistentVolumeClaim</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">database-persistent-volume-claim</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">storageClassName:</span> <span class="hljs-string">manual</span>
  <span class="hljs-attr">accessModes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">ReadWriteOnce</span>
  <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">requests:</span>
      <span class="hljs-attr">storage:</span> <span class="hljs-string">2Gi</span>
</code></pre>
<p>Again, the <code>apiVersion</code>, <code>kind</code>, and <code>metadata</code> serve the same purpose as any other configuration file.</p>
<ul>
<li><code>spec.storageClass</code> in a claim configuration file indicates the type of storage this claim wants. That means any <code>PersistentVolume</code> that has <code>spec.storageClass</code> set to <code>manual</code> is suitable to be consumed by this claim. If you have multiple volumes with the <code>manual</code> class, the claim will get any one of them and if you have no volume with <code>manual</code> class – a volume will be provisioned dynamically.</li>
<li><code>spec.accessModes</code> again sets the access mode here. This indicates that this claim wants a storage that has an <code>accessMode</code> of <code>ReadWriteOnce</code>. Assume that you have two volumes with class set to <code>manual</code>. One of them has its <code>accessModes</code> set to <code>ReadWriteOnce</code> and the other one to <code>ReadWriteMany</code>. This claim will get the one with <code>ReadWriteOnce</code>.</li>
<li><code>resources.requests.storage</code> is the amount of storage this claim wants. <code>2Gi</code> doesn't mean the given volume must have exactly 2 gigabytes of storage capacity. It means that it must have at least 2 gigabytes. I hope you remember that you set the capacity of the persistent volume to be 5 gigabytes, which is more than 2 gigabytes.</li>
</ul>
<p>To apply this file, execute the following command:</p>
<pre><code class="lang-bash">kubectl apply -f database-persistent-volume-claim.yaml

<span class="hljs-comment"># persistentvolumeclaim/database-persistent-volume-claim created</span>
</code></pre>
<p>Now use the <code>get</code> command to verify that the volume has been created:</p>
<pre><code class="lang-bash">kubectl get persistentvolumeclaim

<span class="hljs-comment"># NAME                               STATUS   VOLUME                       CAPACITY   ACCESS MODES   STORAGECLASS   AGE</span>
<span class="hljs-comment"># database-persistent-volume-claim   Bound    database-persistent-volume   5Gi        RWO            manual         37s</span>
</code></pre>
<p>Look at the <code>VOLUME</code> column. This claim is bound to the <code>database-persistent-volume</code> persistent volume that you created earlier. Also look at the <code>CAPACITY</code>. It's <code>5Gi</code>, because the claim requested a volume with at least 2 gigabytes of storage capacity.</p>
<h3 id="heading-dynamic-provisioning-of-persistent-volumes">Dynamic Provisioning of Persistent Volumes</h3>
<p>In the previous sub-section, you've made a persistent volume and then created a claim. But, what if there isn't any persistent volume previously provisioned?</p>
<p>In such cases, a persistent volume compatible with the claim will be provisioned automatically. </p>
<p>To begin this demonstration, remove the previously created persistent volume and the persistent volume claim with the following commands:</p>
<pre><code class="lang-yaml"><span class="hljs-string">kubectl</span> <span class="hljs-string">delete</span> <span class="hljs-string">persistentvolumeclaim</span> <span class="hljs-string">--all</span>

<span class="hljs-comment"># persistentvolumeclaim "database-persistent-volume-claim" deleted</span>

<span class="hljs-string">kubectl</span> <span class="hljs-string">delete</span> <span class="hljs-string">persistentvolumeclaim</span> <span class="hljs-string">--all</span>

<span class="hljs-comment"># persistentvolume "database-persistent-volume" deleted</span>
</code></pre>
<p>Open up the <code>database-persistent-volume-claim.yaml</code> file and update its content to be as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">PersistentVolumeClaim</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">database-persistent-volume-claim</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">accessModes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">ReadWriteOnce</span>
  <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">requests:</span>
      <span class="hljs-attr">storage:</span> <span class="hljs-string">2Gi</span>
</code></pre>
<p>I've removed the <code>spec.storageClass</code> field from the file. Now re-apply the <code>database-persistent-volume-claim.yaml</code> file without applying the <code>database-persistent-volume.yaml</code> file:</p>
<pre><code class="lang-yaml"><span class="hljs-string">kubectl</span> <span class="hljs-string">apply</span> <span class="hljs-string">-f</span> <span class="hljs-string">database-persistent-volume-claim.yaml</span>

<span class="hljs-comment"># persistentvolumeclaim/database-persistent-volume-claim created</span>
</code></pre>
<p>Now use the <code>get</code> command to look at the claim information:</p>
<pre><code class="lang-yaml"><span class="hljs-string">kubectl</span> <span class="hljs-string">get</span> <span class="hljs-string">persistentvolumeclaim</span>

<span class="hljs-comment"># NAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE</span>
<span class="hljs-comment"># database-persistent-volume-claim   Bound    pvc-525ae8af-00d3-4cc7-ae47-866aa13dffd5   2Gi        RWO            standard       2s</span>
</code></pre>
<p>As you can see, a volume with <code>pvc-525ae8af-00d3-4cc7-ae47-866aa13dffd5</code> name and storage capacity of <code>2Gi</code> has been provisioned and bound to the claim dynamically. </p>
<p>You can either use a static or dynamically provisioned persistent volume for the rest of this project. I'll be using a dynamically provisioned one.</p>
<h3 id="heading-connecting-volumes-with-pods">Connecting Volumes with Pods</h3>
<p>Now that you have created a persistent volume and a claim, it's time to let the database pod use this volume. </p>
<p>You do this by connecting the pod to the persistent volume claim you made in the previous sub-section. Open up the <code>postgres-deployment.yaml</code> file and update its content to be as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-comment"># volume configuration for the pod</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-storage</span>
          <span class="hljs-attr">persistentVolumeClaim:</span>
            <span class="hljs-attr">claimName:</span> <span class="hljs-string">database-persistent-volume-claim</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-postgres</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">5432</span>
          <span class="hljs-comment"># volume mounting configuration for the container</span>
          <span class="hljs-attr">volumeMounts:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-storage</span>
              <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/var/lib/postgresql/data</span>
              <span class="hljs-attr">subPath:</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">63eaQB9wtLqmNBpg</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_DB</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">notesdb</span>
</code></pre>
<p>I've added two new fields in this configuration file.</p>
<ul>
<li><code>spec.volumes</code> field contains the necessary information for the pod to find the persistent volume claim. <code>spec.volumes.name</code> can be anything you want. <code>spec.volumes.persistentVolumeClaim.claimName</code> has to match the <code>metadata.name</code> value from the <code>database-persistent-volume-claim.yaml</code> file.</li>
<li><code>containers.volumeMounts</code> contains information necessary for mounting the volume inside the container. <code>containers.volumeMounts.name</code> has to match the value from <code>spec.volumes.name</code>. <code>containers.volumeMounts.mountPath</code> indicates the directory where this volume will be mounted. <code>/var/lib/postgresql/data</code> is the default data directory for PostgreSQL. <code>containers.volumeMounts.subPath</code> indicates a directory that will be created inside the volume. Assume that you're using the same volume with other pods as well. In that case you can put pod-specific data inside another directory inside that volume. All data saved inside the <code>/var/lib/postgresql/data</code> directory will go inside a <code>postgres</code> directory within the volume.</li>
</ul>
<p>Now re-apply the <code>postgres-deployment.yaml</code> file by executing the following command:</p>
<pre><code class="lang-bash">kubectl apply -f postgres-deployment.yaml

<span class="hljs-comment"># deployment.apps/postgres-deployment configured</span>
</code></pre>
<p>Now you have a proper database deployment with a much smaller risk of data loss. </p>
<p>One thing that I would like to mention here is that the database deployment in this project has only one replica. If there were more than one replica, things would have been different. </p>
<p>Multiple pods accessing the same volume without them knowing about each others' existence can bring catastrophic results. In such cases creating sub directories for the pods inside that volume can be a good idea.</p>
<h3 id="heading-wiring-everything-up">Wiring Everything Up</h3>
<p>Now that you have both the API and database running, it's time to finish some unfinished business and set-up the networking. </p>
<p>You've already learned in previous sections that to set up networking in Kubernetes, you use services. Before you start writing the services, have a look at the networking plan that I have for this project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/notes-api-2.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>The database will only be exposed within the cluster using a <code>ClusterIP</code> service. No external traffic will be allowed.</li>
<li>The API deployment, however, will be exposed to the outside world. Users will communicate with the API and the API will communicate with the database.</li>
</ul>
<p>You've previously worked with a <code>LoadBalancer</code> service that exposes an application to the outside world. The <code>ClusterIP</code> on the other hand exposes an application within the cluster and allows no outside traffic.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/cluster-ip-3.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Given that the database service should be available only within the cluster, a <code>ClusterIP</code> service is the perfect fit for this scenario. </p>
<p>Create a new file called <code>postgres-cluster-ip-service.yaml</code> inside the <code>k8s</code> directory and put following content in it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-cluster-ip-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">component:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">5432</span>
</code></pre>
<p>As you can see, the configuration file for a <code>ClusterIP</code> is identical to one for a <code>LoadBalancer</code>. The only thing that differs is the <code>spec.type</code> value. </p>
<p>You should be able to interpret this file without any trouble by now. 5432 is the default port that PostgreSQL runs on. That's why that port has to be exposed.</p>
<p>The next configuration file is for the <code>LoadBalancer</code> service, responsible for exposing the API to the outside world. Create another file called <code>api-load-balancer-service.yaml</code> and put the following content in it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api-load-balancer-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">3000</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">3000</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
</code></pre>
<p>This configuration is identical to the one you've written in a previous section. The API runs in port 3000 inside the container and that's why that port has to be exposed.</p>
<p>The last thing to do is to add the rest of the environment variables to the API deployment. So open up the <code>api-deployment.yaml</code> file and update its content like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-api</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">3000</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_CONNECTION</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">pg</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_HOST</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">postgres-cluster-ip-service</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_PORT</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">'5432'</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_USER</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">postgres</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_DATABASE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">notesdb</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_PASSWORD</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">63eaQB9wtLqmNBpg</span>
</code></pre>
<p>Previously there was just the <code>DB_CONNECTION</code> variable under <code>spec.containers.env</code>. The new variables are as follows:</p>
<ul>
<li><code>DB_HOST</code> indicates the host address for the database service. In a non-containerized environment the value is usually <code>127.0.0.1</code>. But in a Kubernetes environment, you don't know the IP address of the database pod. Hence you just use the service name that exposes the database instead.</li>
<li><code>DB_PORT</code> is the port exposed from the database service, which is 5432.</li>
<li><code>DB_USER</code> is the user for connecting to the database. <code>postgres</code> is the default username.</li>
<li><code>DB_DATABASE</code> is the database that the API will connect to. This has to match with the <code>spec.containers.env.DB_DATABASE</code> value from the <code>postgres-deployment.yaml</code> file.</li>
<li><code>DB_PASSWORD</code> is the password for connecting to the database. This has to match with the <code>spec.containers.env.DB_PASSWORD</code> value from the <code>postgres-deployment.yaml</code> file.</li>
</ul>
<p>With that done, now you're ready to test out the API. Before you do that, I'll suggest applying all the configuration files once again by executing the following command:</p>
<pre><code class="lang-bash">kubectl apply -f k8s

<span class="hljs-comment"># deployment.apps/api-deployment created</span>
<span class="hljs-comment"># service/api-load-balancer-service created</span>
<span class="hljs-comment"># persistentvolumeclaim/database-persistent-volume-claim created</span>
<span class="hljs-comment"># service/postgres-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/postgres-deployment created</span>
</code></pre>
<p>If you face any errors, just delete all resources and re-apply the files. The services, the persistent volumes, and the persistent volume claims should be created instantly. </p>
<p>Use the <code>get</code> command to make sure the deployments are all up and running:</p>
<pre><code class="lang-bash">kubectl get deployment

<span class="hljs-comment"># NAME                  READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="hljs-comment"># api-deployment        3/3     3            3           106s</span>
<span class="hljs-comment"># postgres-deployment   1/1     1            1           106s</span>
</code></pre>
<p>As you can see from the <code>READY</code> column, all the pods are up and running. To access the API, use the <code>service</code> command for <code>minikube</code>.</p>
<pre><code class="lang-bash">minikube service api-load-balancer-service

<span class="hljs-comment"># |-----------|---------------------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># | NAMESPACE |           NAME            | TARGET PORT |             URL             |</span>
<span class="hljs-comment"># |-----------|---------------------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># | default   | api-load-balancer-service |        3000 | http://172.19.186.112:31546 |</span>
<span class="hljs-comment"># |-----------|---------------------------|-------------|-----------------------------|</span>
<span class="hljs-comment"># * Opening service default/api-load-balancer-service in default browser...</span>
</code></pre>
<p>The API should open automatically in your default browser:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-93.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This is the default response for the API. You can also use <a target="_blank" href="http://172.19.186.112:31546/"><code>http://172.19.186.112:31546/</code></a> with some API testing tool like <a target="_blank" href="https://insomnia.rest/">Insomnia</a> or <a target="_blank" href="https://www.postman.com/">Postman</a> to test out the API. The API has full CRUD functionality. </p>
<p>You can see the tests that come with the API source code as documentation. Just open up the <code>api/tests/e2e/api/routes/notes.test.js</code> file. You should be able to understand the file without much hassle if you have experience with JavaScript and <a target="_blank" href="https://expressjs.com/">express</a>.</p>
<h2 id="heading-working-with-ingress-controllers">Working with Ingress Controllers</h2>
<p>So far in this article, you've used <code>ClusterIP</code> to expose an application within the cluster and <code>LoadBalancer</code> to expose an application outside the cluster.</p>
<p>Although I've cited <code>LoadBalancer</code> as the standard service kind for exposing an application outside the cluster, it has some cons. </p>
<p>When using <code>LoadBalancer</code> services to expose applications in cloud environment, you'll have to pay for each exposed services individually which can be expensive in case of huge projects.</p>
<p>There is another kind of service called <code>NodePort</code> that can be used as an alternative to the <code>LoadBalancer</code> kind of services.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/node-port-2.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><code>NodePort</code> opens a specific port on all the nodes in your cluster, and handles any traffic that comes through that open port. </p>
<p>As you already know, services group together a number of pods, and control the way they can be accessed. So any request that reaches the service through the exposed port will end up in the correct pod.</p>
<p>An example configuration file for creating a <code>NodePort</code> can be as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-kube-node-port</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">NodePort</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
      <span class="hljs-attr">nodePort:</span> <span class="hljs-number">31515</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">component:</span> <span class="hljs-string">web</span>
</code></pre>
<p>The <code>spec.ports.nodePort</code> field here must have a value between 30000 and 32767. This range is out of the well-known ports usually used by various services but is also unusual. I mean how many times do you see a port with so many digits?</p>
<blockquote>
<p>You can try to replace the <code>LoadBalancer</code> services you created in the previous sections with a <code>NodePort</code> service. This shouldn't be tough and can be treated as a test for what you've learned so far.</p>
</blockquote>
<p>To solve the issues I've mentioned the <code>Ingress</code> API was created. To be very clear, <code>Ingress</code> is actually not a type of service. Instead, it sits in front of multiple services and acts as a router of sorts.</p>
<p>An <code>IngressController</code> is required to work with <code>Ingress</code> resources in your cluster. A list of avalable ingress controllers can be found in the Kubernetes <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/">documentation</a>.</p>
<h3 id="heading-setting-up-nginx-ingress-controller">Setting up NGINX Ingress Controller</h3>
<p>In this example, you'll extend the notes API by adding a front end to it. And instead of using a service like <code>LoadBalancer</code> or <code>NodePort</code>, you'll use <code>Ingress</code> to expose the application. </p>
<p>The controller you'll be using is the <a target="_blank" href="https://github.com/kubernetes/ingress-nginx/blob/master/README.md">NGINX Ingress Controller</a> because <a target="_blank" href="https://www.nginx.com/">NGINX</a> will be used for routing requests to different services here. The NGINX Ingress Controller makes it very easy to work with NGINX configurations in a Kubernetes cluster.</p>
<p>The code for the project lives inside the <code>fullstack-notes-application</code> directory.</p>
<pre><code>.
├── api
├── client
├── docker-compose.yaml
├── k8s
│   ├── api-deployment.yaml
│   ├── database-persistent-volume-claim.yaml
│   ├── postgres-cluster-ip-service.yaml
│   └── postgres-deployment.yaml
├── nginx
└── postgres

<span class="hljs-number">5</span> directories, <span class="hljs-number">1</span> file
</code></pre><p>You'll see a <code>k8s</code> directory in there. It contains all the configuration files you wrote in the last sub-section, except the <code>api-load-balancer-service.yaml</code> file. </p>
<p>The reason for that is, in this project, the old <code>LoadBalancer</code> service will be replaced with an <code>Ingress</code>. Also, instead of exposing the API, you'll expose the front-end application to the world.</p>
<p>Before you start writing the new configuration files, have a look at how things are going to work behind the scenes. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/fullstack-1.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>A user visits the front-end application and submits the necessary data. The front-end application then forwards the submitted data to the back-end API. </p>
<p>The API then persists the data in the database and also sends it back to the front-end application. Then routing of the requests is achieved using NGINX. </p>
<p>You can have a look at the <code>nginx/production.conf</code> file to understand how this routing has been set-up.</p>
<p>Now the necessary networking required to make this happen is as follows:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/ingress.svg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This diagram can be explained as follows:</p>
<ul>
<li>The <code>Ingress</code> will act as the entry-point and router for this application. This is an <code>NGINX</code> type <code>Ingress</code> so the port will be the default nginx port which is 80.</li>
<li>Every request that comes to <code>/</code> will be routed towards the front-end application (the service on the left). So if the URL for this application is <code>https://kube-notes.test</code>, then any request coming to <code>https://kube-notes.test/foo</code> or <code>https://kube-notes.test/bar</code> will be handled by the front-end application.</li>
<li>Every request that comes to <code>/api</code> will be routed towards the back-end API (the service on the right). So if the URL again is <code>https://kube-notes.test</code>, then any request coming to <code>https://kube-notes.test/api/foo</code> or <code>https://kube-notes.test/api/bar</code> will be handled by the back-end API.</li>
</ul>
<p>It was totally possible to configure the <code>Ingress</code> service to work with sub-domains instead of paths like this, but I chose the path-based approach because that's how my application is designed.</p>
<p>In this sub-section, you'll have to write four new configuration files. </p>
<ul>
<li><code>ClusterIP</code> configuration for the API deployment.</li>
<li><code>Deployment</code> configuration for the front-end application.</li>
<li><code>ClusterIP</code> configuration for the front-end application.</li>
<li><code>Ingress</code> configuration for the routing.</li>
</ul>
<p>I'll go through the first three files very quickly without spending much time explaining them. </p>
<p>The first one is the <code>api-cluster-ip-service.yaml</code> configuration and the contents of the file are as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api-cluster-ip-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">3000</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">3000</span>
</code></pre>
<p>Although in the previous sub-section you exposed the API directly to the outside world, in this one, you'll let the <code>Ingress</code> do the heavy lifting while exposing the API internally using a good old <code>ClusterIP</code> service. </p>
<p>The configuration itself should be pretty self-explanatory at this point, so I won't be spending any time explaining it.</p>
<p>Next, create a file named <code>client-deployment.yaml</code> responsible for running the front-end application. Contents of the file are as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">client-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">client</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">client</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">client</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-client</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">VUE_APP_API_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">/api</span>
</code></pre>
<p>It's almost identical to the the <code>api-deployment.yaml</code> file and I'm assuming that you'll be able to interpret this configuration file by yourself. </p>
<p>The <code>VUE_APP_API_URL</code> environment variable here indicates the path to which the API requests should be forwarded. These forwarded requests will be in turn handled by the <code>Ingress</code>.</p>
<p>To expose this client application internally another <code>ClusterIP</code> service is necessary. Create a new file called <code>client-cluster-ip-service.yaml</code> and put the following content in it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">client-cluster-ip-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">component:</span> <span class="hljs-string">client</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
</code></pre>
<p>All this does is expose port 8080 within the cluster on which the front end application runs by default.</p>
<p>Now that the boring old configurations are done, the next configuration is the <code>ingress-service.yaml</code> file and the content of the file is as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">networking.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Ingress</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">ingress-service</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-attr">kubernetes.io/ingress.class:</span> <span class="hljs-string">nginx</span>
    <span class="hljs-attr">nginx.ingress.kubernetes.io/rewrite-target:</span> <span class="hljs-string">/$1</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">http:</span>
        <span class="hljs-attr">paths:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">pathType:</span> <span class="hljs-string">Prefix</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">"/?(.*)"</span>
          <span class="hljs-attr">backend:</span>
            <span class="hljs-attr">service:</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">client-cluster-ip-service</span>
              <span class="hljs-attr">port:</span> 
                <span class="hljs-attr">number:</span> <span class="hljs-number">8080</span>
        <span class="hljs-bullet">-</span>  <span class="hljs-attr">pathType:</span> <span class="hljs-string">Prefix</span>
           <span class="hljs-attr">path:</span> <span class="hljs-string">"/api/?(.*)"</span>
           <span class="hljs-attr">backend:</span>
             <span class="hljs-attr">service:</span>
              <span class="hljs-attr">name:</span> <span class="hljs-string">api-cluster-ip-service</span>
              <span class="hljs-attr">port:</span>
                <span class="hljs-attr">number:</span> <span class="hljs-number">3000</span>
</code></pre>
<p>This configuration file may look quite a bit unfamiliar to you but it's actually pretty straightforward.</p>
<ul>
<li>The <code>Ingress</code> API is still in beta phase thus the <code>apiVersion</code> is <code>extensions/v1</code>.</li>
<li>The <code>kind</code> and <code>metadata.name</code> fields serve the same purpose as any of the configurations you wrote earlier.</li>
<li><code>metadata.annotations</code> can contain information regarding the <code>Ingress</code> configuration. The <code>kubernetes.io/ingress.class: nginx</code> indicates that the <code>Ingress</code> object should be controlled by the <code>ingress-nginx</code> controller. <code>nginx.ingress.kubernetes.io/rewrite-target</code> indicates that you want to <a target="_blank" href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#rewrite">rewrite</a> the URL target in places.</li>
<li><code>spec.rules.http.paths</code> contains configuration regarding the individual path routings you previously saw inside the <code>nginx/production.conf</code> file. The <code>paths.pathType</code> corresponds to the type of path. By deafult it's <code>Prefix</code> in NGINX. The <code>paths.path</code> field indicates the path that should be routed. <code>backend.serviceName</code> is the service that the aforementioned path should be routed towards and <code>backend.servicePort</code> is the target port inside that service.</li>
<li><code>/?(.*)</code> and <code>/api/?(.*)</code> are simple regex which means that <code>?(.*)</code> part will be routed towards the designated services.</li>
</ul>
<p>The way you configure rewrites can change from time to time, so checking out the official <a target="_blank" href="https://kubernetes.github.io/ingress-nginx/examples/rewrite/">docs</a> would be good idea.</p>
<p>Before you apply the new configurations, you'll have to activate the <code>ingress</code> addon for <code>minikube</code> using the <code>addons</code> command. The generic syntax is as follows:</p>
<pre><code>minikube addons &lt;option&gt; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">addon</span> <span class="hljs-attr">name</span>&gt;</span></span>
</code></pre><p>To activate the <code>ingress</code> addon, execute the following command:</p>
<pre><code class="lang-bash">minikube addons <span class="hljs-built_in">enable</span> ingress

<span class="hljs-comment"># ? Verifying ingress addon...</span>
<span class="hljs-comment"># ? The 'ingress' addon is enabled</span>
</code></pre>
<p>You can use the <code>disable</code> option for the <code>addon</code> command to disable any addon. You can learn more about the <code>addon</code> command in the official <a target="_blank" href="https://minikube.sigs.k8s.io/docs/commands/addons/">docs</a>.</p>
<p>Once the addon has been activated, you may apply the configuration files. I would suggest deleting all resources (services, deployments, and persistent volume claims) before applying the new ones.</p>
<pre><code class="lang-bash">kubectl delete ingress --all

<span class="hljs-comment"># ingress.extensions "ingress-service" deleted</span>

kubectl delete service --all

<span class="hljs-comment"># service "api-cluster-ip-service" deleted</span>
<span class="hljs-comment"># service "client-cluster-ip-service" deleted</span>
<span class="hljs-comment"># service "kubernetes" deleted</span>
<span class="hljs-comment"># service "postgres-cluster-ip-service" deleted</span>

kubectl delete deployment --all

<span class="hljs-comment"># deployment.apps "api-deployment" deleted</span>
<span class="hljs-comment"># deployment.apps "client-deployment" deleted</span>
<span class="hljs-comment"># deployment.apps "postgres-deployment" deleted</span>

kubectl delete persistentvolumeclaim --all

<span class="hljs-comment"># persistentvolumeclaim "database-persistent-volume-claim" deleted</span>

kubectl apply -f k8s

<span class="hljs-comment"># service/api-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/api-deployment created</span>
<span class="hljs-comment"># service/client-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/client-deployment created</span>
<span class="hljs-comment"># persistentvolumeclaim/database-persistent-volume-claim created</span>
<span class="hljs-comment"># ingress.extensions/ingress-service created</span>
<span class="hljs-comment"># service/postgres-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/postgres-deployment created</span>
</code></pre>
<p>Wait until all the resources have been created. You can utilize the <code>get</code> command to ensure that. Once all of them are running, you can access the application at the IP address of the <code>minikube</code> cluster. To get the IP, you can execute the following command:</p>
<pre><code class="lang-bash">minikube ip

<span class="hljs-comment"># 172.17.0.2</span>
</code></pre>
<p>You can also get this IP address by running inspecting the <code>Ingress</code>:</p>
<pre><code class="lang-bash">kubectl get ingress

<span class="hljs-comment"># NAME              CLASS    HOSTS   ADDRESS      PORTS   AGE</span>
<span class="hljs-comment"># ingress-service   &lt;none&gt;   *       172.17.0.2   80      2m33s</span>
</code></pre>
<p>As you can see, the IP and port is visible under the <code>ADDRESS</code> and <code>PORTS</code> columns. By accessing <code>127.17.0.2:80</code>, you should land directly on the notes application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-84.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can perform simple CRUD operations in this application. Port 80 is the default port for NGINX, so you don't need to write the port number in the URL. </p>
<p>You can do a lot with this ingress controller if you know how to configure NGINX. After all, that's what this controller is used for – storing NGINX configurations on a Kubernetes <code>ConfigMap</code>, which you'll be learning about in the next sub-section.</p>
<h3 id="heading-secrets-and-config-maps-in-kubernetes">Secrets and Config Maps in Kubernetes</h3>
<p>So far in your deployments, you've stored sensitive information such as     <code>POSTGRES_PASSWORD</code> in plain text, which is not a very good idea. </p>
<p>To store such values in your cluster you can use a <code>Secret</code> which is a much more secure way of storing passwords, tokens, and so on.</p>
<blockquote>
<p>The next step may not work the same in the Windows command line. You can use <a target="_blank" href="https://git-scm.com/">git</a> bash or <a target="_blank" href="https://cmder.net/">cmder</a> for the task.</p>
</blockquote>
<p>To store information in a <code>Secret</code> you have to first pass your data through base64. If the plain text password is <code>63eaQB9wtLqmNBpg</code> then execute following command to get a base64 encoded version:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> -n <span class="hljs-string">"63eaQB9wtLqmNBpg"</span> | base64

<span class="hljs-comment"># NjNlYVFCOXd0THFtTkJwZw==</span>
</code></pre>
<p>This step is not optional, you have to run the plain text string through base64. Now create a file named <code>postgres-secret.yaml</code> inside the <code>k8s</code> directory and put following content in there:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Secret</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">NjNlYVFCOXd0THFtTkJwZw==</span>
</code></pre>
<p>The <code>apiVersion</code>, <code>kind</code>, and <code>metadata</code> fields are pretty self-explanatory. The <code>data</code> field holds the actual secret. </p>
<p>As you can see, I've created a key-value pair where the key is <code>password</code> and the value is <code>NjNlYVFCOXd0THFtTkJwZw==</code>. You'll be using the <code>metadata.name</code> value to identify this <code>Secret</code> in other configuration files and the key to access the password value.</p>
<p>Now to use this secret inside your the database configuration, update the <code>postgres-deployment.yaml</code> file as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-storage</span>
          <span class="hljs-attr">persistentVolumeClaim:</span>
            <span class="hljs-attr">claimName:</span> <span class="hljs-string">database-persistent-volume-claim</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-postgres</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">5432</span>
          <span class="hljs-attr">volumeMounts:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-storage</span>
              <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/var/lib/postgresql/data</span>
              <span class="hljs-attr">subPath:</span> <span class="hljs-string">postgres</span>
          <span class="hljs-attr">env:</span>
              <span class="hljs-comment"># not putting the password directly anymore</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_PASSWORD</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">password</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">POSTGRES_DB</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">notesdb</span>
</code></pre>
<p>As you can see, the entire file is the same except the <code>spec.template.spec.continers.env</code> field. </p>
<p>The <code>name</code> environment variable used to store the password value was in plain text before. But now there is a new <code>valueFrom.secretKeyRef</code> field. </p>
<p>The <code>name</code> field here refers to the name of the <code>Secret</code> you created moments ago, and the <code>key</code> value refers to the key from the key-value pair in that <code>Secret</code> configuration file. The encoded value will be decoded to plain text internally by Kubernetes.</p>
<p>Apart from the database configuration, you'll also have to update the <code>api-deployment.yaml</code> file as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-api</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">3000</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_CONNECTION</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">pg</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_HOST</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">postgres-cluster-ip-service</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_PORT</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">'5432'</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_USER</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">postgres</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_DATABASE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">notesdb</span>
              <span class="hljs-comment"># not putting the password directly anymore</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_PASSWORD</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">password</span>
</code></pre>
<p>Now apply all these new configurations by executing the following command:</p>
<pre><code class="lang-bash">kubectl apply -f k8s

<span class="hljs-comment"># service/api-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/api-deployment created</span>
<span class="hljs-comment"># service/client-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/client-deployment created</span>
<span class="hljs-comment"># persistentvolumeclaim/database-persistent-volume-claim created</span>
<span class="hljs-comment"># secret/postgres-secret created</span>
<span class="hljs-comment"># ingress.extensions/ingress-service created</span>
<span class="hljs-comment"># service/postgres-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/postgres-deployment created</span>
</code></pre>
<p>Depending on the state of your cluster, you may see a different set of output.</p>
<blockquote>
<p>In case you're having any issue, delete all Kubernetes resources and create them again by applying the configs.</p>
</blockquote>
<p>Use the <code>get</code> command to inspect and make sure all the pods are up and running. </p>
<p>Now to test out the new configuration, access the notes application using the <code>minikube</code> IP and try creating new notes. To get the IP, you can execute the following command:</p>
<pre><code class="lang-bash">minikube ip

<span class="hljs-comment"># 172.17.0.2</span>
</code></pre>
<p>By accessing <code>127.17.0.2:80</code>, you should land directly on the notes application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-92.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>There is another way to create secrets without any configuration file. To create the same <code>Secret</code> using <code>kubectl</code>, execute the following command:</p>
<pre><code class="lang-bash">kubectl create secret generic postgres-secret --from-literal=password=63eaQB9wtLqmNBpg

<span class="hljs-comment"># secret/postgres-secret created</span>
</code></pre>
<p>This is a more convenient approach as you can skip the whole base64 encoding step. The secret in this case will be encoded automatically.</p>
<p>A <code>ConfigMap</code> is similar to a <code>Secret</code> but is meant to be used with non sensitive information. </p>
<p>To put all the other environment variables in the API deployment inside a <code>ConfigMap</code>, create a new file called <code>api-config-map.yaml</code> inside the <code>k8s</code> directory and put following content in it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span> 
<span class="hljs-attr">kind:</span> <span class="hljs-string">ConfigMap</span> 
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api-config-map</span> 
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">DB_CONNECTION:</span> <span class="hljs-string">pg</span>
  <span class="hljs-attr">DB_HOST:</span> <span class="hljs-string">postgres-cluster-ip-service</span>
  <span class="hljs-attr">DB_PORT:</span> <span class="hljs-string">'5432'</span>
  <span class="hljs-attr">DB_USER:</span> <span class="hljs-string">postgres</span>
  <span class="hljs-attr">DB_DATABASE:</span> <span class="hljs-string">notesdb</span>
</code></pre>
<p><code>apiVersion</code>, <code>kind</code> and <code>metadata</code> are again self-explanatory. The <code>data</code> field can hold the environment variables as key-value pairs. </p>
<p>Unlike the <code>Secret</code>, the keys here have to match the exact key required by the API. Thus, I have sort of copied the variables from <code>api-deployment.yaml</code> file and pasted them here with a slight modification in the syntax.</p>
<p>To make use of this secret in the API deployment, open up the <code>api-deployment.yaml</code> file and update its content as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">api</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-api</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">3000</span>
          <span class="hljs-comment"># not putting environment variables directly</span>
          <span class="hljs-attr">envFrom:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">configMapRef:</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">api-config-map</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_PASSWORD</span>
              <span class="hljs-attr">valueFrom:</span>
                <span class="hljs-attr">secretKeyRef:</span>
                  <span class="hljs-attr">name:</span> <span class="hljs-string">postgres-secret</span>
                  <span class="hljs-attr">key:</span> <span class="hljs-string">password</span>
</code></pre>
<p>The entire file is almost unchanged except the <code>spec.template.spec.containers.env</code> field. </p>
<p>I have moved the environment variables to the <code>ConfigMap</code>. <code>spec.template.spec.containers.envFrom</code> is used to get data from a <code>ConfigMap</code>. <code>configMapRef.name</code> here indicates the <code>ConfigMap</code> from where the environment variables will be pulled.</p>
<p>Now apply all these new configurations by executing the following command:</p>
<pre><code class="lang-bash">kubectl apply -f k8s

<span class="hljs-comment"># service/api-cluster-ip-service created</span>
<span class="hljs-comment"># configmap/api-config-map created</span>
<span class="hljs-comment"># deployment.apps/api-deployment created</span>
<span class="hljs-comment"># service/client-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/client-deployment created</span>
<span class="hljs-comment"># persistentvolumeclaim/database-persistent-volume-claim created</span>
<span class="hljs-comment"># ingress.extensions/ingress-service configured</span>
<span class="hljs-comment"># service/postgres-cluster-ip-service created</span>
<span class="hljs-comment"># deployment.apps/postgres-deployment created</span>
<span class="hljs-comment"># secret/postgres-secret created</span>
</code></pre>
<p>Depending on the state of your cluster, you may see a different set of output.</p>
<blockquote>
<p>In case you're having any issue, delete all Kubernetes resources and create them again by applying the configs.</p>
</blockquote>
<p>Upon making sure that the pods are up and running using the <code>get</code> command, access the notes application using the <code>minikube</code> IP and try creating new notes. </p>
<p>To get the IP, you can execute the following command:</p>
<pre><code class="lang-bash">minikube ip

<span class="hljs-comment"># 172.17.0.2</span>
</code></pre>
<p>By accessing <code>127.17.0.2:80</code>, you should land directly on the notes application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-92.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><code>Secret</code> and <code>ConfigMap</code> have a few more tricks up their sleeves that I'm not going to get into right now. But if you're curious, you may check out the official <a target="_blank" href="https://kubectl.docs.kubernetes.io/pages/app_management/secrets_and_configmaps.html">docs</a>.</p>
<h3 id="heading-performing-update-rollouts-in-kubernetes">Performing Update Rollouts in Kubernetes</h3>
<p>Now that you've successfully deployed an application consisting of multiple containers on Kubernetes, it's time to learn about performing updates.</p>
<p>As magical as Kubernetes may seem to you, updating a container to a newer image version is a bit of a pain. There are multiple approaches that people often take to update a container, but I am not going to go through all of them.</p>
<p>Instead, I'll jump right into the approach that I mostly take in updating my containers. If you open up the <code>client-deployment.yaml</code> file and look into the <code>spec.template.spec.containers</code> field, you'll find something that looks like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">client</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-client</span>
</code></pre>
<p>As you can see, in the <code>image</code> field I haven't used any image tag. Now if you think that adding <code>:latest</code> at the end of the image name will ensure that the deployment always pulls the latest available image, you'd be dead wrong.</p>
<p>The approach that I usually take is an imperative one. I've already mentioned in a previous section that, in a few cases, using an imperative approach instead of a declarative one is a good idea. Creating a <code>Secret</code> or updating a container is such a case.</p>
<p>The command you can use to perform the update is the <code>set</code> command, and the generic syntax is as follows:</p>
<pre><code>kubectl set image &lt;resource type&gt;/&lt;resource name&gt; &lt;container name&gt;=&lt;image name with tag&gt;
</code></pre><p>The resource type is <code>deployment</code> and resource name is <code>client-deployment</code>. The container name can be found under the <code>containers</code> field inside the <code>client-deployment.yaml</code> file, which is <code>client</code> in this case. </p>
<p>I have already build a version of the <code>fhsinchy/notes-client</code> image with a tag of <code>edge</code> that I'll be using to update this deployment.</p>
<p>So the final command should be as follows:</p>
<pre><code class="lang-bash">kubectl <span class="hljs-built_in">set</span> image deployment/client-deployment client=fhsinchy/notes-client:edge

<span class="hljs-comment"># deployment.apps/client-deployment image updated</span>
</code></pre>
<p>The update process may take a while, as Kubernetes will recreate all the pods. You can run the <code>get</code> command to know if all the pods are up and running again. </p>
<p>Once they've all been recreated, access the notes application using the <code>minikube</code> IP and try creating new notes. To get the IP, you can execute the following command:</p>
<pre><code class="lang-bash">minikube ip

<span class="hljs-comment"># 172.17.0.2</span>
</code></pre>
<p>By accessing <code>127.17.0.2:80</code>, you should land directly on the notes application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/image-92.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Given that I haven't made any actual changes to the application code, everything will remain the same. You can ensure that the pods are using the new image using the <code>describe</code> command.</p>
<pre><code class="lang-bash">kubectl describe pod client-deployment-849bc58bcc-gz26b | grep <span class="hljs-string">'Image'</span>

<span class="hljs-comment"># Image:          fhsinchy/notes-client:edge</span>
<span class="hljs-comment"># Image ID:       docker-pullable://fhsinchy/notes-client@sha256:58bce38c16376df0f6d1320554a56df772e30a568d251b007506fd3b5eb8d7c2</span>
</code></pre>
<p>The <code>grep</code> command is available on Mac and Linux. If you're on Windows, use git bash instead of the windows command line. </p>
<p>Although the imperative update process is a bit tedious, but it can be made much easier by using a good CI/CD workflow.</p>
<h3 id="heading-combining-configurations">Combining Configurations</h3>
<p>As you've already seen, the number of configuration files in this project is pretty huge despite only having three containers in it.</p>
<p>You can actually combine configuration files as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">client-deployment</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">component:</span> <span class="hljs-string">client</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">component:</span> <span class="hljs-string">client</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">client</span>
          <span class="hljs-attr">image:</span> <span class="hljs-string">fhsinchy/notes-client</span>
          <span class="hljs-attr">ports:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">VUE_APP_API_URL</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">/api</span>

<span class="hljs-meta">---</span>

<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">client-cluster-ip-service</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">ClusterIP</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">component:</span> <span class="hljs-string">client</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
      <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
</code></pre>
<p>As you can see, I've combined the contents of the <code>client-deployment.yaml</code> and <code>client-cluster-ip-service.yaml</code> file using a delimiter (<code>---</code>). Although it's possible and can help in projects where the number of containers is very high, I recommend keeping them separate, clean, and concise.</p>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<p>In this section, I'll be listing some common issues that you may face during your time with Kubernetes.</p>
<ul>
<li>If you're on Windows or Mac and using the Docker driver for <code>minikube</code>, the <code>Ingress</code> plugin will not work.</li>
<li>If you have <a target="_blank" href="https://laravel.com/docs/7.x/valet">Laravel Valet</a> running on Mac and are using the HyperKit driver for <code>minikube</code>, it'll fail to connect to the internet. Turning off the <code>dnsmasq</code> service will resolve the issue.</li>
<li>If you have a Ryzen (mine is R5 1600) PC and are running Windows 10, the VirtualBox driver may fail to start due to the lack of support for nested virtualization. You'll have to use the Hyper-V driver on Windows 10 (Pro, Enterprise, and Education). For the Home edition users, sadly there is no safe option on that hardware.</li>
<li>If you're running Windows 10 (Pro, Enterprise, and Education) with the Hyper-V driver for <code>minikube</code>, the VM may fail to start with a message regarding insufficient memory. Don't panic, and execute the <code>minikube start</code> command once again to start the VM properly.</li>
<li>If you see some of the commands executed in this article missing or misbehaving in the Windows command line, use <a target="_blank" href="https://git-scm.com/">git</a> bash or <a target="_blank" href="https://cmder.net/">cmder</a> instead.</li>
<li>If you're on a Raspberry Pi, my images won't work. Use the images built by <a target="_blank" href="https://github.com/RaedsLab">Raed Chammam</a>. All three images can be found on his <a target="_blank" href="https://hub.docker.com/u/raed667">Docker Hub Profile</a>. Instructions regarding image building for Raspberry Pi can be found in this <a target="_blank" href="https://github.com/fhsinchy/kubernetes-handbook-projects/issues/2#issue-899658948">GitHub Issue</a>.</li>
</ul>
<p>I would suggest installing a good Linux distribution on your system and using the Docker driver for <code>minikube</code>. This is by far the fastest and most reliable set-up.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I would like to thank you from the bottom of my heart for the time you've spent reading this article. I hope you've enjoyed your time and have learned all the essentials of Kubernetes.</p>
<p>Apart from this one, I've written full-length handbooks on other complicated topics available for free on <a target="_blank" href="https://www.freecodecamp.org/news/author/farhanhasin/">freeCodeCamp</a>.</p>
<p>These handbooks are part of my mission to simplify hard to understand technologies for everyone. Each of these handbooks takes a lot of time and effort to write.</p>
<p>If you've enjoyed my writing and want to keep me motivated, consider leaving starts on <a target="_blank" href="https://github.com/fhsinchy/">GitHub</a> and endorse me for relevant skills on <a target="_blank" href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. I also accept sponsorship so you may consider <a target="_blank" href="https://www.buymeacoffee.com/farhanhasin">buying me a coffee</a> if you want to.</p>
<p>I'm always open to suggestions and discussions on <a target="_blank" href="https://twitter.com/frhnhsin">Twitter</a> or <a target="_blank" href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. Hit me with direct messages.</p>
<p>In the end, consider sharing the resources with others, because </p>
<blockquote>
<p>Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something. — Richard Stallman</p>
</blockquote>
<p>Till the next one, stay safe and keep learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to self-host a Hugo web app ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff After hosting with Netlify for a few years, I decided to head back to self hosting. There are a few reasons for that, but the main reasoning was that I had more control over how things worked. In this post, I'll show you my workflow fo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/my-latest-self-hosted-hugo-workflow/</link>
                <guid isPermaLink="false">66d850624c5150361c4fdb2e</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ FreeBSD ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Hugo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ self hosting ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sun, 29 Mar 2020 18:10:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/03/Self-hosted-Hugo.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p>After hosting with Netlify for a few years, I decided to head back to self hosting. There are a few reasons for that, but the main reasoning was that I had more control over how things worked.</p>
<p>In this post, I'll show you my workflow for deploying my <a target="_blank" href="https://gohugo.io">Hugo</a> generated site (<a target="_blank" href="https://www.jaredwolff.com">www.jaredwolff.com</a>). </p>
<p>Instead of using what most people would go for, I'll be doing all of this using a FreeBSD Jails-based server. Plus I'll show you some tricks I've learned over the years on bulk image resizing and more.</p>
<p>Let's get to it.</p>
<h2 id="heading-where-to-host">Where to host?</h2>
<p>If you want to host your own service, you'll need a server. That's where a VPS provider like Digital Ocean or Vultr comes in. I've been a fan and have used Digital Ocean for a while now.</p>
<p>To set up a new server here are some steps:</p>
<ol>
<li>Login to Digital Ocean. If you don’t have Digital Ocean and would like to support this blog click <a target="_blank" href="https://m.do.co/c/9574d3846a29">here</a> to create an account.</li>
<li>Go to <code>Account Settings</code> -&gt; <code>Security</code> and make sure you have an SSH key setup.</li>
<li>Create a new FreeBSD droplet. Make sure you use the UFS version <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.41.21_AM.png" alt="Create Droplet" width="730" height="471" loading="lazy"> <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.43.21_AM.png" alt="Choose FreeBSD 12.1 UFS" width="730" height="471" loading="lazy"></li>
<li>Make sure you select the $5 a month plan. For simple installs, this is more than enough! <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.44.13_AM.png" alt="$5 Plan" width="730" height="471" loading="lazy"></li>
<li>Make sure your SSH key is selected <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.45.26_AM.png" alt="Select SSH key" width="730" height="465" loading="lazy"></li>
<li>Finally click that green <strong>Create Droplet</strong> button! <img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Screen_Shot_2020-04-15_at_9.45.24_AM.png" alt="Create droplet" width="730" height="465" loading="lazy"></li>
<li>SSH in once you’re done: <code>ssh root@&lt;yourserverip&gt;</code></li>
</ol>
<h2 id="heading-setting-up-your-freebsd-server-with-bastille">Setting up your FreeBSD server with Bastille</h2>
<p><img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/bastille.png" alt="images/bastille.png" width="730" height="486" loading="lazy"></p>
<p>Up until recently, everything was running on a Docker based platform using <a target="_blank" href="https://github.com/exoframejs/exoframe">Exoframe</a>. It was easy and almost brainless. </p>
<p>The downside was that Docker takes up wayyyy too many resources. Plus managing files within a Docker container is as much or more work than hosting it natively. Oh, and have you checked how much space Docker has been using on your machine lately? On my development machine its was about 19GB of space. ?</p>
<p>So what's the alternative?</p>
<p>FreeBSD Jails using Bastille.</p>
<p>I've been playing with Bastille for a few months now. The more I use it, the more it makes 100% sense.</p>
<p>Bastille allows you to create (now) portable lightweight FreeBSD based jails. These jails are "containers" that have virtually no overhead. There's no daemon (the operating system is the "daemon"!). Plus, jails are secure compared to the can of worms that Docker is. Yes, you may have to compile and port some utilities. Most though are already supported in FreeBSD's package manager <code>pkg</code>.</p>
<p>In this section you'll learn how to get a jail running with <code>caddy</code> so you can securely host your site.</p>
<p>Let's keep the momentum going!</p>
<p>Once you get the IP address for your server, you should login:</p>
<p>    ssh root@123.456.789.10</p>
<p>You should get a MOTD message and an <code>sh</code> prompt. Woo!</p>
<p>    FreeBSD 12.1-RELEASE-p2 GENERIC</p>
<p>    Welcome to FreeBSD!
    ...</p>
<p>    #</p>
<p>Let's install a few important bits using <code>pkg</code> (FreeBSD's package manager):</p>
<p>    pkg install restic rsync bastille</p>
<p>We'll be using <code>restic</code> for backups, <code>rsync</code> for transferring files and <code>bastille</code> for jail setup.</p>
<p>You also have to set up some static routes in your <code>pf.conf</code>. Here's an example of mine:</p>
<pre><code class="lang-shell">ext_if="vtnet0"

# Caddy related
caddy_addr=10.10.2.20

set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo

table &lt;jails&gt; persist
nat on $ext_if from &lt;jails&gt; to any -&gt; $ext_if

# container routes
rdr pass inet proto tcp from any to port 80 -&gt; $caddy_addr port 8880
rdr pass inet proto tcp from any to port 443 -&gt; $caddy_addr port 4443

# Enable dynamic rdr (see below)
rdr-anchor "rdr/*"

block in all
pass out quick modulate state
antispoof for $ext_if inet
pass in inet proto tcp from any to any port ssh flags S/SA keep state
</code></pre>
<p>This is a standard <code>pf.conf</code> file for <code>bastille</code>.  Make sure you edit <code>caddy_addr</code> to the IP you chose.</p>
<p>Now let's start the firewall. You will get kicked out of your <code>ssh</code> session:</p>
<pre><code>sysrc pf_enable=<span class="hljs-string">"YES"</span>
service pf start
</code></pre><p>Then let's get some <code>bastille</code> configuration out of the way:</p>
<pre><code># set up bastille networking
sysrc cloned_interfaces+=lo1
sysrc ifconfig_lo1_name=<span class="hljs-string">"bastille0"</span>
service netif cloneup

# bootstrap the base jail and start bastille
bastille bootstrap <span class="hljs-number">12.1</span>-RELEASE update
sysrc bastille_enable=<span class="hljs-string">"YES"</span>
service bastille start
</code></pre><p>This will set up your networking, and fetch the latest default base jail you'll use later.</p>
<p>Next, let's set up the jail:</p>
<pre><code>bastille create caddy <span class="hljs-number">12.1</span>-STABLE <span class="hljs-number">10.10</span><span class="hljs-number">.2</span><span class="hljs-number">.20</span>
bastille start caddy
</code></pre><p>Then install <code>caddy</code></p>
<pre><code>#install the binary
fetch https:<span class="hljs-comment">//github.com/caddyserver/caddy/releases/download/v1.0.4/caddy_v1.0.4_freebsd_amd64.tar.gz</span>
tar xvf caddy_v1<span class="hljs-number">.0</span><span class="hljs-number">.4</span>_freebsd_amd64.tar.gz caddy
bastille cp caddy caddy /usr/local/bin/
rm caddy

#create the caddy user
bastille cmd caddy pw useradd caddy -m -s /usr/sbin/nologin

#install ca root file
bastille pkg caddy install ca_root_nss
</code></pre><p>When installing <code>ca_root_nss</code> , <code>pkg</code> will have to initialize. Accept the prompts. Once you're done here we'll move on to the next step!</p>
<p>Once installation is complete, we should also configure <code>caddy</code> to start on boot. The easiest way to do that is use this <code>rc.d</code> script:</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/sh</span>

<span class="hljs-comment"># $FreeBSD: head/net/caddy/files/caddy.in 452063 2017-10-14 12:58:24Z riggs $</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># PROVIDE: caddy</span>
<span class="hljs-comment"># REQUIRE: LOGIN</span>
<span class="hljs-comment"># KEYWORD: shutdown</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Add the following lines to /etc/rc.conf.local or /etc/rc.conf</span>
<span class="hljs-comment"># to enable this service:</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># caddy_enable (bool):    Set to NO by default.</span>
<span class="hljs-comment">#                Set it to YES to enable caddy.</span>
<span class="hljs-comment"># caddy_user (user):        Set user to run caddy.</span>
<span class="hljs-comment">#                Default is "caddy".</span>
<span class="hljs-comment"># caddy_group (group):    Set group to run caddy.</span>
<span class="hljs-comment">#                Default is "caddy".</span>
<span class="hljs-comment"># caddy_conf (path):        Path to caddy configuration file.</span>
<span class="hljs-comment">#                Default is /usr/local/etc/caddyfile.conf</span>

. /etc/rc.subr

name=caddy
rcvar=caddy_enable

load_rc_config <span class="hljs-variable">$name</span>

: <span class="hljs-variable">${caddy_enable:="NO"}</span>
: <span class="hljs-variable">${caddy_user:="caddy"}</span>
: <span class="hljs-variable">${caddy_group:="caddy"}</span>
: <span class="hljs-variable">${caddy_conf:="/usr/local/etc/caddyfile.conf"}</span>
: <span class="hljs-variable">${caddy_log:="/home/caddy/caddy.log"}</span>
: <span class="hljs-variable">${caddy_env:="CADDYPATH=/home/caddy/"}</span>
: <span class="hljs-variable">${caddy_https_port:="4443"}</span>
: <span class="hljs-variable">${caddy_http_port:="8880"}</span>

pidfile=<span class="hljs-string">"/var/run/caddy.pid"</span>
procname=<span class="hljs-string">"/usr/local/bin/caddy"</span>
<span class="hljs-built_in">command</span>=<span class="hljs-string">"/usr/sbin/daemon"</span>
command_args=<span class="hljs-string">"-f -p <span class="hljs-variable">${pidfile}</span> /usr/bin/env <span class="hljs-variable">${caddy_env}</span> <span class="hljs-variable">${procname}</span> -agree -http-port <span class="hljs-variable">${caddy_http_port}</span>  -https-port <span class="hljs-variable">${caddy_https_port}</span> -conf=<span class="hljs-variable">${caddy_conf}</span> -log=<span class="hljs-variable">${caddy_log}</span> <span class="hljs-variable">${caddy_args}</span>"</span>
extra_commands=<span class="hljs-string">"reload"</span>

start_precmd=caddy_startprecmd
reload_cmd=caddy_reloadcmd

<span class="hljs-function"><span class="hljs-title">caddy_startprecmd</span></span>()
{
      <span class="hljs-keyword">if</span> [ ! -e <span class="hljs-variable">${pidfile}</span> ]; <span class="hljs-keyword">then</span>
              install -o <span class="hljs-variable">${caddy_user}</span> -g <span class="hljs-variable">${caddy_group}</span> /dev/null <span class="hljs-variable">${pidfile}</span>;
      <span class="hljs-keyword">fi</span>
}

<span class="hljs-function"><span class="hljs-title">caddy_reloadcmd</span></span>()
{
      <span class="hljs-built_in">kill</span> -s USR1 $(cat <span class="hljs-variable">${pidfile}</span>)
}

run_rc_command <span class="hljs-string">"<span class="hljs-variable">$1</span>"</span>
</code></pre>
<p>Remove the <code>caddy</code> executable if you haven't already. Then create a new file with <code>vi</code>. This will be your <code>rc.d</code> script!</p>
<pre><code>vi caddy
</code></pre><p>Then paste the contents of the above script in there, save and exit.</p>
<p>Make sure the file is executable by using <code>chmod</code> and copy to the Caddy container.</p>
<pre><code>chmod +x caddy
bastille cp caddy caddy /usr/local/etc/rc.d/
</code></pre><p>Finally, we'll need a Caddyfile. Here's an example of one:</p>
<pre><code>stage.jaredwolff.com {
  tls hello@jaredwolff.com
  log /home/caddy/stage.jaredwolff.com.log
  root /<span class="hljs-keyword">var</span>/www/stage.jaredwolff.com/
  gzip
  log stderr
}
</code></pre><p><code>log</code> refers to this site specific access log.</p>
<p><code>root</code> refers to where the root <code>public</code> folder is on your machine. In my case it's the common <code>/var/www/&lt;name of site&gt;</code>. Set your paths and remember them. We'll need them later!</p>
<p>To have Caddy generate certs for this subdomain, you'll have to set the <em>tls</em> option. An email is all that's needed.</p>
<p>For more on the Caddyfile structure <a target="_blank" href="https://caddyserver.com/docs/caddyfile">check out the documentation.</a></p>
<p>Make a file called <code>caddyfile.conf</code> and copy it to <code>/usr/local/etc/</code> in your Caddy container:</p>
<pre><code>vi caddyfile.conf
# Paste your caddyfile contents and save
bastille cp caddy caddyfile.conf /usr/local/etc/
</code></pre><p>You should now redirect your DNS to the server IP. That way Caddy can generate/fetch the correct certificates. Then you can start Caddy with:</p>
<pre><code>bastille service caddy caddy start
</code></pre><p>You can check the log at <code>/usr/home/caddy/caddy.log</code> to make sure that your domain provisioned correctly.</p>
<p><strong><em>Side note:</em></strong> Getting setup with SSL certs is tough at first, especially if you're migrating from another server. Your site will have to go down for a little bit while you switch your DNS settings and start <code>caddy</code>. </p>
<p>(That's if you're using standard <code>caddy</code> 1.0. You can also use the DNS provider <a target="_blank" href="https://github.com/caddyserver/dnsproviders">plugins here</a> which make things a little easier.)</p>
<p>Now that we have <code>caddy</code> up and running it's time to copy our <code>hugo</code> generated assets over using <code>rsync</code>. We're off to the next step!</p>
<h2 id="heading-make-building-and-deploying-easy"><em>Make</em> building and deploying easy</h2>
<p><img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/make.png" alt="images/make.png" width="730" height="486" loading="lazy"></p>
<p>I spend a ton of time writing C code, and that means I spend tons of time using Makefiles. For many, <code>make</code> (or <code>gmake</code> for GNU make) is the bane of their existence. </p>
<p>For building and deploying, <code>make</code> makes it easy to create reusable recipes. That way you know you can deploy with confidence every time.</p>
<p>My Makefile borrows from the one that <a target="_blank" href="https://victoria.dev/blog/a-portable-makefile-for-continuous-delivery-with-hugo-and-github-pages/">Victoria Drake had posted</a> not too long ago. I changed it up a bit to match my needs. </p>
<p>Let's take a tour and see what's inside:</p>
<pre><code class="lang-Makefile"><span class="hljs-section">.POSIX:</span>

HUGO_VERSION := 0.66.0

OPTIMIZED_DIR := optimized
CONTENT_DIR := content
DEST_DIR := public

SERVER := 123.456.789.10
USER := user
</code></pre>
<p>The first section contains all the variables that I use to tell the functions later on what to do. It also has a reference to the <code>.POSIX</code> target. This means that the Makefile will be as portable between different versions of <code>make</code>.</p>
<p>Then, I popped in some logic to determine whether I'm deploying to <em>stage</em> or <em>production:</em></p>
<pre><code class="lang-Makefile"><span class="hljs-comment"># Set the place where it's deployed to.</span>
<span class="hljs-keyword">ifdef</span> PRODUCTION
<span class="hljs-variable">$(info Building for production. ?)</span>
TARGET := www
<span class="hljs-keyword">else</span>
<span class="hljs-variable">$(info Building for development. ?)</span>
BASEURL := --baseURL <span class="hljs-string">"https://stage.jaredwolff.com"</span>
TARGET := stage
<span class="hljs-keyword">endif</span>
</code></pre>
<p>By default, recipes below will use the development workflow. To use the production workflow, you can invoke <code>make</code> like this:</p>
<pre><code class="lang-Makefile">PRODUCTION=1 make build
</code></pre>
<p>This does add some extra friction to the deploy process. It's a good step though. That way you're sure the deploy is going to the right place!</p>
<pre><code class="lang-Makefile"><span class="hljs-comment"># Full path</span>
DEPLOY_DIR := /usr/local/bastille/jails/caddy/root/path/to/<span class="hljs-variable">$(TARGET)</span>.jaredwolff.com
</code></pre>
<p>Using the <code>TARGET</code> variable above, I then define the path to my server assets. I'm using Bastille to organize my jails, so the path is extra long. (yea, lengthly long) This allows us to use <code>rsync</code> to deploy the files with ease.</p>
<p>Now here come the fun bits. To do a full bulk resize, I'm using the <code>wildcard</code> functionality of the Makefile.</p>
<pre><code class="lang-Makefile">IMAGES := \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.jpg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.JPG)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.jpeg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.png)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.jpg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.jpeg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.png)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.JPG)</span> \
</code></pre>
<p>In this case it will create a huge space delimited list of every image that is within my content directory. The biggest drawback of this method is that it's not space tolerant. An easy fix to this is to make sure that all my photos do not have spaces.</p>
<p>Here's a quick and dirty bash command. You can use to rename files that have spaces and replace them with '_' characters:</p>
<pre><code class="lang-Makefile">for f in *\ *; do mv <span class="hljs-string">"$f"</span> <span class="hljs-string">"${f// /_}"</span>; done
</code></pre>
<p>Next, we rename these entries so the prefix is now the target directory. This will be useful when we want to resize:</p>
<pre><code class="lang-Makefile">OPTIMIZED_IMAGES := \
<span class="hljs-variable">$(<span class="hljs-built_in">subst</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/,<span class="hljs-variable">$(OPTIMIZED_DIR)</span>/,<span class="hljs-variable">$(IMAGES)</span>)</span>
</code></pre>
<p>Now check out the <code>optimize</code> recipe:</p>
<pre><code class="lang-Makefile"><span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: optimize</span>
<span class="hljs-section">optimize: build <span class="hljs-variable">$(OPTIMIZED_IMAGES)</span></span>
@echo <span class="hljs-string">"? Optimizing images"</span>
rsync -r <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/ <span class="hljs-variable">$(DEST_DIR)</span>/
du -sh <span class="hljs-variable">$(CONTENT_DIR)</span>/
du -sh <span class="hljs-variable">$(DEST_DIR)</span>/

<span class="hljs-variable">$(OPTIMIZED_IMAGES)</span>:
convert -strip -compress JPEG -resize '730&gt;' <span class="hljs-variable">$(<span class="hljs-built_in">subst</span> <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/,<span class="hljs-variable">$(CONTENT_DIR)</span>/,<span class="hljs-variable">$@</span>)</span> <span class="hljs-variable">$@</span>
</code></pre>
<p>It first calls the <code>build</code> recipe and then also the <code>$(OPTIMIZED_IMAGES)</code> recipe. The later will optimize the image using the <code>convert</code> command from <a target="_blank" href="https://imagemagick.org/script/convert.php">Imagemagick</a>. In this case I'm only resizing files that are larger than 730px wide. Change yours accordingly so you can reap the benefits of an <a target="_blank" href="https://www.jaredwolff.com/seven-ways-to-optimize-your-site-for-speed/">optimized site.</a></p>
<p>After resizing, the recipe uses <code>rsync</code> to copy the files from the <code>OPTIMIZED_DIR</code> to <code>DEST_DIR.</code></p>
<p>If we take a look at the <code>build</code> recipe, I first building the assets. Then, I copy the photos from the <code>content</code> dir to <code>optimized</code> dir. The nice thing is that <code>rsync</code> will only move files that have changed. Thus it doesn't have to copy the files over and over and over again every time you build.</p>
<p>Finally, the <code>deploy</code> recipe.</p>
<pre><code class="lang-Makefile"><span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: deploy</span>
<span class="hljs-section">deploy:</span>
@echo rsync to <span class="hljs-variable">$(DEPLOY_DIR)</span>
@rsync -r --del public/ <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>:<span class="hljs-variable">$(DEPLOY_DIR)</span>/
@echo making restic snapshot
@scp scripts/backup.sh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>:/root/backup.sh
@ssh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span> sh /root/backup.sh <span class="hljs-variable">$(DEPLOY_DIR)</span>
@echo <span class="hljs-string">"? Site is deployed!"</span>
</code></pre>
<p>You can see again that I'm using rsync to sync the contents of <code>public/</code> to the server. Make sure you set the <code>USER</code> , <code>SERVER</code> and <code>DEPLOY_DIR</code>. In my case <code>DEPLOY_DIR</code> comes out to <code>/usr/local/bastille/jails/caddy/root/var/www/www.jaredwolff.com</code></p>
<p>When you do finally get a successful deploy you can double check everything is in the correct place. Then once everything looks good you can start up your caddy server using:</p>
<pre><code>bastille service caddy caddy start
</code></pre><p><code>deploy</code> will also do something extra handy here. It will deploy my <code>restic</code> backup script and run it. I'll talk about this more in the backup section.</p>
<p>All in all, here's the full Makefile:</p>
<pre><code class="lang-Makefile"><span class="hljs-section">.POSIX:</span>

HUGO_VERSION := 0.66.0

OPTIMIZED_DIR := optimized
CONTENT_DIR := content
DEST_DIR := public

SERVER := 155.138.230.8
USER := root

<span class="hljs-comment"># Set the place where it's deployed to.</span>
<span class="hljs-keyword">ifdef</span> PRODUCTION
<span class="hljs-variable">$(info Building for production. ?)</span>
TARGET := www
<span class="hljs-keyword">else</span>
<span class="hljs-variable">$(info Building for development. ?)</span>
BASEURL := --baseURL <span class="hljs-string">"https://stage.jaredwolff.com"</span>
TARGET := stage
<span class="hljs-keyword">endif</span>

<span class="hljs-comment"># Full path</span>
DEPLOY_DIR := /usr/local/bastille/jails/caddy/root/var/www/<span class="hljs-variable">$(TARGET)</span>.jaredwolff.com

IMAGES := \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.jpg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.JPG)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.jpeg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/images/*.png)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.jpg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.jpeg)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.png)</span> \
<span class="hljs-variable">$(<span class="hljs-built_in">wildcard</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/*/*/images/*.JPG)</span> \

OPTIMIZED_IMAGES := \
<span class="hljs-variable">$(<span class="hljs-built_in">subst</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/,<span class="hljs-variable">$(OPTIMIZED_DIR)</span>/,<span class="hljs-variable">$(IMAGES)</span>)</span>

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: all</span>
<span class="hljs-section">all: build optimize</span>

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: clean</span>
<span class="hljs-section">clean:</span>
rm -rf public/
rm -rf optimized/

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: serve</span>
<span class="hljs-section">serve:</span>
@hugo serve -D

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: ssh</span>
<span class="hljs-section">ssh:</span>
@ssh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: build</span>
<span class="hljs-section">build:</span>
@echo <span class="hljs-string">"? Generating site"</span>
hugo --gc --minify -d <span class="hljs-variable">$(DEST_DIR)</span> <span class="hljs-variable">$(BASEURL)</span>
rsync -av --del -f<span class="hljs-string">"+ */"</span> -f<span class="hljs-string">"- *"</span> <span class="hljs-variable">$(CONTENT_DIR)</span>/ <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: optimize</span>
<span class="hljs-section">optimize: build <span class="hljs-variable">$(OPTIMIZED_IMAGES)</span></span>
@echo <span class="hljs-string">"? Optimizing images"</span>
rsync -r <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/ <span class="hljs-variable">$(DEST_DIR)</span>/
du -sh <span class="hljs-variable">$(CONTENT_DIR)</span>/
du -sh <span class="hljs-variable">$(DEST_DIR)</span>/

<span class="hljs-variable">$(OPTIMIZED_IMAGES)</span>:
convert -strip -compress JPEG -resize '730&gt;' <span class="hljs-variable">$(<span class="hljs-built_in">subst</span> <span class="hljs-variable">$(OPTIMIZED_DIR)</span>/,<span class="hljs-variable">$(CONTENT_DIR)</span>/,<span class="hljs-variable">$@</span>)</span> <span class="hljs-variable">$@</span>

<span class="hljs-meta"><span class="hljs-meta-keyword">.PHONY</span>: deploy</span>
<span class="hljs-section">deploy:</span>
@echo rsync to <span class="hljs-variable">$(DEPLOY_DIR)</span>
@rsync -r --del public/ <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>:<span class="hljs-variable">$(DEPLOY_DIR)</span>/
@echo making restic snapshot
@scp scripts/backup.sh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span>:/root/backup.sh
@ssh <span class="hljs-variable">$(USER)</span>@<span class="hljs-variable">$(SERVER)</span> sh /root/backup.sh <span class="hljs-variable">$(DEPLOY_DIR)</span>
@echo <span class="hljs-string">"? Site is deployed!"</span>
</code></pre>
<p>There are a few other handy nuggets in there you may want to use.  <code>clean</code>, <code>serve</code> and <code>ssh</code> have been very helpful when testing and connecting.</p>
<p>In the end you'll have a two step deploy process. The first generates your site with optimized images. The second is deploying to a server for static hosting.</p>
<h2 id="heading-incremental-backup">Incremental Backup</h2>
<p><img src="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/images/Backup.png" alt="images/Backup.png" width="730" height="486" loading="lazy"></p>
<p>After discovering <a target="_blank" href="https://restic.net">Restic</a> I've been sold on how handy it has been for all my incremental backup needs. In the case of my server, I'm using to back up the root folder of my site. That way, if I need to roll back, I can do so with a few short steps.</p>
<p>Here's how you can set up a local <code>restic</code> repo.</p>
<h3 id="heading-setting-it-up">Setting it up</h3>
<p>Initializing the repo is simple. The most important part is making sure you <strong>don't lose/forget your password!</strong></p>
<pre><code>    # restic init -r /root/backups
    enter password <span class="hljs-keyword">for</span> <span class="hljs-keyword">new</span> repository:
    enter password again:
    created restic repository <span class="hljs-number">32e14</span>c7052 at /root/backups

    Please note that knowledge <span class="hljs-keyword">of</span> your password is required to access
    the repository. Losing your password means that your data is
    irrecoverably lost.
</code></pre><p>Set the <code>RESTIC_PASSWORD</code> environment variable to avoid entering your password. To make it permanent you'll have to place <code>export RESTIC_PASSWORD="Your password here!"</code> within the <code>.profile</code> file in <code>/root/</code>.</p>
<h3 id="heading-backing-up">Backing Up</h3>
<p>Invoking <code>restic</code> over SSH is tough. So our next best bet?</p>
<p>Transfer a (very brief) shell script to the server and run it after a deploy. Here's the contents of what I'm using today:</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/sh</span>
<span class="hljs-built_in">export</span> RESTIC_PASSWORD=<span class="hljs-string">"Your password here!"</span>
restic backup <span class="hljs-variable">$1</span> -r /root/backups/
</code></pre>
<p><strong><em>Side note:</em></strong> As I sit here and look at this script, for security reasons you can replace "Your password here!" with $2 which is the second argument to the script. That way you don't need to commit/push the password stored in a static file!</p>
<p>This first sets your backup password. Then it runs <code>restic</code> using the first command line argument as the path. So, to run a backup with this script, it would look something like this:</p>
<pre><code>./backup.sh /path/to/your/public/folder/
</code></pre><p><strong>Note:</strong> you do need to initialize your <code>restic</code> backup <em>before</em> you start backing up. It will barf at you otherwise!</p>
<p>In my case I'm placing the incremental backups on a different folder of my machine. That way they're easily accessible and <em>fast</em>.</p>
<h3 id="heading-viewing-your-backups">Viewing your backups</h3>
<p>To view your backups you can run the following command:</p>
<h1 id="heading-restic-snapshots-r-rootbackups-g-paths-c">restic snapshots -r /root/backups -g paths -c</h1>
<p>    enter password for repository:
    repository e140b5e4 opened successfully, password is correct
    snapshots for (paths [/usr/local/bastille/jails/caddy/root/var/www/www.jaredwolff.com]):</p>
<h2 id="heading-id-time-host-tags">    ID        Time                 Host         Tags</h2>
<p>    d3328066  2020-03-10 00:30:58  vultr.guest
    f3360819  2020-03-10 04:03:03  vultr.guest
    231dd134  2020-03-10 04:44:00  vultr.guest
    3c1be26a  2020-03-10 04:56:19  vultr.guest
    e96c947c  2020-03-10 05:03:00  vultr.guest
    34c3682a  2020-03-10 14:01:37  vultr.guest
    fbccdb8c  2020-03-10 14:04:26  vultr.guest
    9ce11146  2020-03-10 15:38:49  vultr.guest
    046b3da3  2020-03-10 15:47:06  vultr.guest
    9c28d4bc  2020-03-10 15:48:25  vultr.guest
    469dc228  2020-03-10 15:48:54  vultr.guest
    6f78af72  2020-03-10 17:00:21  vultr.guest
    29ad17b2  2020-03-10 20:18:23  vultr.guest
    ed22ce1f  2020-03-10 20:20:24  vultr.guest
    9c8c1b03  2020-03-11 13:56:40  vultr.guest
    b6cfcfec  2020-03-11 14:08:14  vultr.guest
    e8546005  2020-03-11 14:27:22  vultr.guest
    49a134fe  2020-03-17 00:47:58  vultr.guest</p>
<h2 id="heading-c0beb283-2020-03-18-204452-vultrguest">    c0beb283  2020-03-18 20:44:52  vultr.guest</h2>
<p>You can use this list to determine if you need to roll back a deploy.</p>
<h3 id="heading-restoring">Restoring</h3>
<p>Restoring from a backup, especially in a live environment, needs to be quick. After viewing your backups you can restore a specific backup by using its <em>ID</em>.</p>
<pre><code>restic restore d3328066
</code></pre><p>This will restore the files back to the backup made on <em>2020-03-10 00:30:58.</em> Awesome. Plus it won't overwrite every single file. It will only apply the differences from the current state and the stored state.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We've covered a ton of ground in this post. You've learned how to:</p>
<ul>
<li>Deploy your own server using Vultr</li>
<li>Use Bastille to create Container-like Jails</li>
<li>Set up Caddy to serve static file assets with TLS</li>
<li>Deploy the files using a fairly simple Makefile and <code>rsync</code></li>
<li>Back up after every deploy using <code>restic</code></li>
</ul>
<p>In the end we have a robust, secure and simple platform for hosting static files and services. </p>
<p>Stay tuned as there are more posts like this coming your way soon! In the meantime check out my <a target="_blank" href="https://www.jaredwolff.com/blog/">other posts.</a> </p>
<p>Thanks for reading and see you next time! ?</p>
<p><strong>You can find other articles like this at <a target="_blank" href="https://www.jaredwolff.com/my-latest-self-hosted-hugo-workflow/">www.jaredwolff.com.</a></strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Where are Docker Images Stored? Docker Container Paths Explained ]]>
                </title>
                <description>
                    <![CDATA[ By Sebastian Sigl Docker has been widely adopted and is used to run and scale applications in production. Additionally, it can be used to start applications quickly by executing a single Docker command.  Companies also are investing more and more eff... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/where-are-docker-images-stored-docker-container-paths-explained/</link>
                <guid isPermaLink="false">66d4610a3bc3ab877dae2234</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ virtualization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 06 Feb 2020 19:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/02/example-of-examples-word-embeddings_grey.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sebastian Sigl</p>
<p>Docker has been widely adopted and is used to run and scale applications in production. Additionally, it can be used to start applications quickly by executing a single Docker command. </p>
<p>Companies also are investing more and more effort into improving development in local and remote Docker containers, which comes with a lot of advantages as well.</p>
<p>You can get the basic information about your Docker configuration by executing:</p>
<pre><code class="lang-shell">$ docker info

...
 Storage Driver: overlay2
 Docker Root Dir: /var/lib/docker
...
</code></pre>
<p>The output contains information about your storage driver and your docker root directory.</p>
<h2 id="heading-the-storage-location-of-docker-images-and-containers">The storage location of Docker images and containers</h2>
<p>A Docker container consists of network settings, volumes, and images. The location of Docker files depends on your operating system. Here is an overview for the most used operating systems:  </p>
<ul>
<li>Ubuntu: <code>/var/lib/docker/</code></li>
<li>Fedora: <code>/var/lib/docker/</code></li>
<li>Debian: <code>/var/lib/docker/</code></li>
<li>Windows: <code>C:\ProgramData\DockerDesktop</code></li>
<li>MacOS: <code>~/Library/Containers/com.docker.docker/Data/vms/0/</code></li>
</ul>
<p>In macOS and Windows, Docker runs Linux containers in a virtual environment. Therefore, there are some additional things to know.</p>
<h3 id="heading-docker-for-mac">Docker for Mac</h3>
<p>Docker is not natively compatible with macOS, so <a target="_blank" href="https://github.com/moby/hyperkit">Hyperkit</a> is used to run a virtual image. Its virtual image data is located in:  </p>
<p><code>~/Library/Containers/com.docker.docker/Data/vms/0</code></p>
<p>Within the virtual image, the path is the default Docker path <code>/var/lib/docker</code>.</p>
<p>You can investigate your Docker root directory by creating a shell in the virtual environment:</p>
<pre><code class="lang-shell">$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
</code></pre>
<p>You can kill this session by pressing <strong>Ctrl+a</strong>, followed by pressing <strong>k</strong> and <strong>y</strong>.</p>
<h3 id="heading-docker-for-windows">Docker for Windows</h3>
<p>On Windows, Docker is a bit fractioned. There are native Windows containers that work similarly to Linux containers. Linux containers are run in a minimal Hyper-V based virtual environment.</p>
<p>The configuration and the virtual image to execute linux images are saved in the default Docker root folder.</p>
<p><code>C:\ProgramData\DockerDesktop</code></p>
<p>If you inspect regular images then you will get linux paths like:</p>
<pre><code class="lang-shell">$ docker inspect nginx

...
"UpperDir": "/var/lib/docker/overlay2/585...9eb/diff"
...
</code></pre>
<p>You can connect to the virtual image by:</p>
<pre><code class="lang-shell">docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -i sh
</code></pre>
<p>There, you can go to the referenced location:</p>
<pre><code class="lang-shell">$ cd /var/lib/docker/overlay2/585...9eb/
$ ls -lah

drwx------    4 root     root        4.0K Feb  6 06:56 .
drwx------   13 root     root        4.0K Feb  6 09:17 ..
drwxr-xr-x    3 root     root        4.0K Feb  6 06:56 diff
-rw-r--r--    1 root     root          26 Feb  6 06:56 link
-rw-r--r--    1 root     root          57 Feb  6 06:56 lower
drwx------    2 root     root        4.0K Feb  6 06:56 work
</code></pre>
<h2 id="heading-the-internal-structure-of-the-docker-root-folder">The internal structure of the Docker root folder</h2>
<p>Inside <code>/var/lib/docker</code>, different information is stored. For example, data for containers, volumes, builds, networks, and clusters.</p>
<pre><code class="lang-shell">$ ls -la /var/lib/docker

total 152
drwx--x--x   15 root     root          4096 Feb  1 13:09 .
drwxr-xr-x   13 root     root          4096 Aug  1  2019 ..
drwx------    2 root     root          4096 May 20  2019 builder
drwx------    4 root     root          4096 May 20  2019 buildkit
drwx------    3 root     root          4096 May 20  2019 containerd
drwx------    2 root     root         12288 Feb  3 19:35 containers
drwx------    3 root     root          4096 May 20  2019 image
drwxr-x---    3 root     root          4096 May 20  2019 network
drwx------    6 root     root         77824 Feb  3 19:37 overlay2
drwx------    4 root     root          4096 May 20  2019 plugins
drwx------    2 root     root          4096 Feb  1 13:09 runtimes
drwx------    2 root     root          4096 May 20  2019 swarm
drwx------    2 root     root          4096 Feb  3 19:37 tmp
drwx------    2 root     root          4096 May 20  2019 trust
drwx------   15 root     root         12288 Feb  3 19:35 volumes
</code></pre>
<h3 id="heading-docker-images">Docker images</h3>
<p>The heaviest contents are usually images. If you use the default storage driver overlay2, then your Docker images are stored in <code>/var/lib/docker/overlay2</code>. There, you can find different files that represent read-only layers of a Docker image and a layer on top of it that contains your changes.</p>
<p>Let’s explore the content by using an example:</p>
<pre><code class="lang-shell">$ docker image pull nginx
$ docker image inspect nginx

[
    {
        "Id": "sha256:207...6e1",
        "RepoTags": [
            "nginx:latest"
        ],
        "RepoDigests": [
            "nginx@sha256:ad5...c6f"
        ],
        "Parent": "",
 ...
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 126698063,
        "VirtualSize": 126698063,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/585...9eb/diff:
                             /var/lib/docker/overlay2/585...9eb/diff",
                "MergedDir": "/var/lib/docker/overlay2/585...9eb/merged",
                "UpperDir": "/var/lib/docker/overlay2/585...9eb/diff",
                "WorkDir": "/var/lib/docker/overlay2/585...9eb/work"
            },
...
</code></pre>
<p>The <strong>LowerDir</strong> contains the read-only layers of an image. The read-write layer that represents changes are part of the <strong>UpperDir</strong>. In my case, the NGINX <strong>UpperDir</strong> folder contains the log files:</p>
<pre><code class="lang-shell">$ ls -la /var/lib/docker/overlay2/585...9eb/diff

total 8
drwxr-xr-x    2 root     root    4096 Feb  2 08:06 .
drwxr-xr-x    3 root     root    4096 Feb  2 08:06 ..
lrwxrwxrwx    1 root     root      11 Feb  2 08:06 access.log -&gt; /dev/stdout
lrwxrwxrwx    1 root     root      11 Feb  2 08:06 error.log -&gt; /dev/stderr
</code></pre>
<p>The <strong>MergedDir</strong> represents the result of the <strong>UpperDir</strong> and <strong>LowerDir</strong> that is used by Docker to run the container. The <strong>WorkDir</strong> is an internal directory for overlay2 and should be empty.</p>
<h3 id="heading-docker-volumes">Docker Volumes</h3>
<p>It is possible to add a persistent store to containers to keep data longer than the container exists or to share the volume with the host or with other containers. A container can be started with a volume by using the <strong>-v</strong> option:</p>
<pre><code class="lang-shell">$ docker run --name nginx_container -v /var/log nginx
</code></pre>
<p>We can get information about the connected volume location by:</p>
<pre><code class="lang-shell">$ docker inspect nginx_container

...
"Mounts": [
            {
                "Type": "volume",
                "Name": "1e4...d9c",
                "Source": "/var/lib/docker/volumes/1e4...d9c/_data",
                "Destination": "/var/log",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
...
</code></pre>
<p>The referenced directory contains files from the location <code>/var/log</code> of the NGINX container.</p>
<pre><code class="lang-shell">$ ls -lah /var/lib/docker/volumes/1e4...d9c/_data

total 88
drwxr-xr-x    4 root     root        4.0K Feb  3 21:02 .
drwxr-xr-x    3 root     root        4.0K Feb  3 21:02 ..
drwxr-xr-x    2 root     root        4.0K Feb  3 21:02 apt
-rw-rw----    1 root     43             0 Jan 30 00:00 btmp
-rw-r--r--    1 root     root       34.7K Feb  2 08:06 dpkg.log
-rw-r--r--    1 root     root        3.2K Feb  2 08:06 faillog
-rw-rw-r--    1 root     43         29.1K Feb  2 08:06 lastlog
drwxr-xr-x    2 root     root        4.0K Feb  3 21:02 nginx
-rw-rw-r--    1 root     43             0 Jan 30 00:00 w
</code></pre>
<h2 id="heading-clean-up-space-used-by-docker">Clean up space used by Docker</h2>
<p>It is recommended to use the Docker command to clean up unused containers. Container, networks, images, and the build cache can be cleaned up by executing:</p>
<pre><code class="lang-shell">$ docker system prune -a
</code></pre>
<p>Additionally, you can also remove unused volumes by executing:</p>
<pre><code class="lang-shell">$ docker volumes prune
</code></pre>
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>Docker is an important part of many people’s environments and tooling. Sometimes, Docker feels a bit like magic by solving issues in a very smart way without telling the user how things are done behind the scenes. Still, Docker is a regular tool that stores its heavy parts in locations that can be opened and changed. </p>
<p>Sometimes, storage can fill up quickly. Therefore, it’s useful to inspect its root folder, but it is not recommended to delete or change any files manually. Instead, the prune commands can be used to free up disk space.</p>
<p>I hope you enjoyed the article. If you like it and feel the need for a round of applause, <a target="_blank" href="https://twitter.com/sesigl">follow me on Twitter</a>. I work at eBay Kleinanzeigen, one of the biggest classified companies globally. By the way, <a target="_blank" href="https://jobs.ebayclassifiedsgroup.com/ebay-kleinanzeigen">we are hiring</a>!</p>
<p>Happy Docker exploring :)</p>
<h2 id="heading-references">References</h2>
<ul>
<li>Docker storagediver documentation<br><a target="_blank" href="https://docs.docker.com/storage/storagedriver/">https://docs.docker.com/storage/storagedriver/</a></li>
<li>Documentation Overlay filesystem<br><a target="_blank" href="https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt">https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Docker Image Guide: How to Delete Docker Images, Stop Containers, and Remove all Volumes ]]>
                </title>
                <description>
                    <![CDATA[ By Sebastian Sigl Docker has been widely adopted and is a great vehicle to deploy an application to the cloud (or some other Docker-ready infrastructure). It is also useful for local development. You can start complex applications quickly, develop in... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-image-guide-how-to-remove-and-delete-docker-images-stop-containers-and-remove-all-volumes/</link>
                <guid isPermaLink="false">66d460f27df3a1f32ee7f895</guid>
                
                    <category>
                        <![CDATA[ beginners guide ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 30 Jan 2020 01:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/01/docker-container-volumes-images.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sebastian Sigl</p>
<p>Docker has been widely adopted and is a great vehicle to deploy an application to the cloud (or some other Docker-ready infrastructure). It is also useful for local development. You can start complex applications quickly, develop in isolation, and still have a very good performance.</p>
<p>Here are the most important commands to use Docker in your daily business efficiently.</p>
<h2 id="heading-list-all-docker-images">List All Docker Images</h2>
<pre><code class="lang-shell">docker images
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-52.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In my case, I have 3 images installed:</p>
<ul>
<li>MySQL, version 8.0.19, one tagged as latest version</li>
<li>and Cassandra with the latest version.</li>
</ul>
<p>To get more information about an image, you can inspect it:</p>
<pre><code class="lang-shell">docker inspect mysql:latest
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-51.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This will return a list of information. Alternatively, you can also use the image ID to get the information:</p>
<pre><code class="lang-shell">docker inspect 3a5e53f63281
</code></pre>
<p>The output can be overwhelming. Therefore, there is a handy option to filter certain information:</p>
<pre><code class="lang-shell">docker inspect --format='{{.RepoTags}}  {{.Config.Image}}' 3a5e53f63281
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-50.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-remove-docker-images">Remove Docker Images</h2>
<p>A single image can be removed by:</p>
<pre><code class="lang-shell">docker rm mysql:latest
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-48.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In my case, the image is still tagged with <em>mysql:8.0.19</em>. Therefore, to remove it completely, I need to also remove another version tag:</p>
<pre><code class="lang-shell">docker rm mysql:8.0.19
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-45.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To remove the image directly, it is easier to delete the image by image id:</p>
<pre><code class="lang-shell">docker image rm 3a5e53f63281 -f
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-46.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The option <strong>-f</strong> forces the execution, because otherwise you would get an error if the image is referenced by more than 1 tag.</p>
<h2 id="heading-start-a-docker-image">Start a Docker Image</h2>
<p>An image can be started in the foreground by:</p>
<pre><code class="lang-shell">docker run cassandra
</code></pre>
<p>If the image does not exist, then it will be downloaded. You can stop the execution by pressing <strong>CTRL+C</strong>. You can also run it in the background by adding the <strong>-d</strong> option:</p>
<pre><code class="lang-shell">docker run -d mysql
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-54.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If the container is started in the background, then you receive the container ID.</p>
<p>By default, the container runs in isolation. Therefore, you won't be able do any communication with it, and no files are stored in your current directory.</p>
<h2 id="heading-forward-ports-of-a-container">Forward ports of a container</h2>
<p>You can forward ports by using the <strong>-p</strong> option to, for example, a page that is exposed from your container:</p>
<pre><code class="lang-shelll">docker run -p 8080:80 nginx
</code></pre>
<p>This NGINX container exposes a webserver on port 80. By using the -p 8080:80, the local port 8080 is forwarded to the container port 80.</p>
<h2 id="heading-log-into-a-container">Log into a container</h2>
<p>Sometimes it is helpful to log into a container. This is only possible if the container has a shell installed. You will get an error if this is not the case.</p>
<p>First, start the container detached and give it a name:</p>
<pre><code class="lang-shell">docker run -d --name my_container nginx
</code></pre>
<p>This will return a container ID. Now you can execute a shell in the container and attach your input and output to it by using the options <strong>-i</strong> and <strong>-t</strong>:</p>
<pre><code class="lang-shell">docker exec -it my_container bash
</code></pre>
<p>Instead of the container name, you can also use the returned container ID for all following operations. Sometimes, bash is not available. Therefore, you can also try to launch a basic shell:</p>
<pre><code class="lang-shell">docker exec -it my_container sh
</code></pre>
<h2 id="heading-list-running-containers">List running containers</h2>
<p>After you’ve started a container, you can see all running containers executing:</p>
<pre><code class="lang-shell">docker ps
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-56.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>By appending <strong>-a</strong>, exited containers will also be listed:</p>
<pre><code class="lang-shell">docker ps -a
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/01/image-57.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-share-a-local-folder-with-a-container">Share a local folder with a container</h2>
<p>Sometimes it is useful to sync files between the container and the local filesystem. You can do it by running a container and using the <strong>-v</strong> option. On Linux and macOS, you can share a local temporary folder with a container by:</p>
<pre><code class="lang-shell">docker run --name=my_container -d -v $(pwd)/tmp:/var/log/nginx -p 8080:80 nginx
</code></pre>
<p>On windows you can run:</p>
<pre><code class="lang-shell">docker run --name=my_container -d -v %cd%/tmp:/var/log/nginx -p 8080:80 nginx
</code></pre>
<h2 id="heading-stop-running-containers">Stop running containers</h2>
<p>It is possible to stop a running container by:</p>
<pre><code class="lang-shell">docker stop my_container
</code></pre>
<p>Stopping a container stops all processes but keeps changes within the filesystem.</p>
<h2 id="heading-start-a-stopped-container">Start a stopped container</h2>
<p>A stopped container can be started by:</p>
<pre><code class="lang-shell">docker start my_container
</code></pre>
<h2 id="heading-remove-a-container">Remove a container</h2>
<p>To remove a stopped container, you can execute:</p>
<pre><code class="lang-shell">docker rm my_container
</code></pre>
<p>To stop and remove the container in one command, you can add the force option <strong>-f</strong>.</p>
<pre><code class="lang-shell">docker rm -f my_container
</code></pre>
<h2 id="heading-create-a-volume-and-share-it-with-multiple-containers">Create a volume and share it with multiple containers</h2>
<p>An independent volume named <strong>SharedData</strong> can be created by:</p>
<pre><code class="lang-shell">docker volume create --name SharedData

docker run --name=my_container -d -v SharedData:/var/log/nginx -p 8080:80 nginx

docker run --name=my_container_2 -d -v SharedData:/var/log/nginx -p 8080:80 nginx
</code></pre>
<p>Both containers will have a shared folder, and files will be synced between both containers.</p>
<h2 id="heading-remove-a-volume">Remove a volume</h2>
<p>To remove a volume, all containers that use the volume need to be removed.</p>
<pre><code class="lang-shell">docker rm -f my_container
docker rm -f my_container_2
docker volume rm SharedData
</code></pre>
<h2 id="heading-remove-stopped-containers-and-unused-images">Remove stopped containers and unused images</h2>
<p>A safe tidy-up command is:</p>
<pre><code class="lang-shell">docker system prune -a
</code></pre>
<h2 id="heading-remove-all-unused-volumes">Remove all unused volumes</h2>
<p>All unmounted volumes can be removed by:</p>
<pre><code class="lang-shell">docker volume prune
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating containers, logging into containers, forwarding ports, and sharing volumes are the most important commands of your Docker command line interface. They build the foundation of systems like Kubernetes and enable us to create and run applications in isolation.</p>
<p>I hope you enjoyed the article. If you like it and feel the need for a round of applause, <a target="_blank" href="https://twitter.com/sesigl">follow me on Twitter</a>.</p>
<p>I am a co-founder of our revolutionary journey platform called <a target="_blank" href="https://www.urlaubsbaron.de">Explore The World</a>. We are a young startup located in Dresden, Germany and will target the German market first. Reach out to me if you have feedback and questions about any topic.</p>
<p>Happy Docker exploring :)</p>
<p>References</p>
<ul>
<li>Docker command line documentation<br><a target="_blank" href="https://docs.docker.com/engine/reference/commandline/docker/">https://docs.docker.com/engine/reference/commandline/docker/</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Docker Data Containers ]]>
                </title>
                <description>
                    <![CDATA[ By Faizan Bashir There is more than one way to manage data in Docker container. Say hello to the Data Containers. Simply put data containers are containers whose job is just to store/manage data. Similar to other containers they are managed by the ho... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-data-containers/</link>
                <guid isPermaLink="false">66d45ef09208fb118cc6cf9b</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker Containers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 05 Jul 2019 18:05:19 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/1_AUiK5PwnsPG_xaT9jcVoSA-2.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Faizan Bashir</p>
<p>There is more than one way to manage data in Docker container. Say hello to the Data Containers.</p>
<p>Simply put data containers are containers whose job is just to store/manage data.</p>
<p>Similar to other containers they are managed by the host system. However, they don’t show up when you perform a <code>docker ps</code> command.</p>
<p>To create a Data Container we first create a container with a well-known name for future reference. We use <em>busybox</em> as the base as it’s small and lightweight in case we want to explore and move the container to another host.</p>
<p>When creating the container, we also provide a volume <code>-v</code> option to define where other containers will be reading/writing data.</p>
<pre><code>$ docker create -v /config --name dataContainer busybox
</code></pre><p>With the container in place, we can now copy files from our local client directory into the container.</p>
<p>To copy files into a container you use the command <code>docker cp</code>. The following command will copy the <em>config.conf</em> file into the <em>config</em> directory of <em>dataContainer</em>.</p>
<pre><code>$ docker cp config.conf dataContainer:<span class="hljs-regexp">/config/</span>
</code></pre><p>Now our Data Container has our config, we can reference the container when we launch dependent containers requiring the configuration file.</p>
<p>Using the magical <code>--volumes-from &lt;container&gt;</code> option we can use the mount volumes from other containers inside the container being launched. In this case, we’ll launch an Ubuntu container which has reference to our Data Container. When we list the config directory, it will show the files from the attached container.</p>
<pre><code>$ docker run --volumes-<span class="hljs-keyword">from</span> dataContainer ubuntu ls/config
</code></pre><p>If a <em>/config</em> directory already existed then, the volumes-from would override and be the directory used. You can map multiple volumes to a container.</p>
<hr>
<h3 id="heading-import-and-export-container-data"><strong>Import and Export Container data</strong></h3>
<p>Data can be imported and exported from a container, using the <code>docker export</code> command.</p>
<p>We can move the Data Container to another machine simply by exporting it to a .tar file.</p>
<pre><code>$ docker <span class="hljs-keyword">export</span> dataContainer &gt; dataContainer.tar
</code></pre><p>Likewise we can import the Data Container back into Docker.</p>
<pre><code>$ docker <span class="hljs-keyword">import</span> dataContainer.tar
</code></pre> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
