<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: opscanopy</title>
    <description>The latest articles on DEV Community by opscanopy (@opscanopy).</description>
    <link>https://dev.to/opscanopy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3982315%2F76cf6c25-3b03-4a82-9a14-c0f39bab7ea1.png</url>
      <title>DEV Community: opscanopy</title>
      <link>https://dev.to/opscanopy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/opscanopy"/>
    <language>en</language>
    <item>
      <title>Kubernetes for DevOps: Pods, Deployments, Services &amp; More</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Wed, 19 Aug 2026 15:31:42 +0000</pubDate>
      <link>https://dev.to/opscanopy/kubernetes-for-devops-pods-deployments-services-more-22af</link>
      <guid>https://dev.to/opscanopy/kubernetes-for-devops-pods-deployments-services-more-22af</guid>
      <description>&lt;p&gt;Kubernetes (K8s) has become the standard runtime fabric for containerised workloads. Whether you are deploying a single microservice or coordinating hundreds of them across multiple availability zones, K8s gives you a consistent, declarative API to describe what should run, where it should run, and how it should behave. This guide walks through the core concepts a DevOps engineer needs day-to-day — cluster architecture, workload primitives, networking, configuration, observability, and troubleshooting — with working YAML and &lt;code&gt;kubectl&lt;/code&gt; commands throughout. To see where Kubernetes fits in the broader learning journey, check the &lt;a href="https://opscanopy.com/learn/roadmaps/kubernetes/" rel="noopener noreferrer"&gt;Kubernetes roadmap&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Kubernetes?
&lt;/h2&gt;

&lt;p&gt;Running containers in production with plain Docker quickly exposes a set of operational problems: who restarts a crashed container, how do you roll out a new image version without downtime, how do you balance traffic across multiple replicas, and how do you ensure a container on a node with 64 GB RAM does not starve a neighbouring container of memory?&lt;/p&gt;

&lt;p&gt;Kubernetes solves these problems through the &lt;strong&gt;desired-state model&lt;/strong&gt;: you tell the cluster what the end state should look like (a YAML manifest), and controllers continuously reconcile actual state toward that goal. This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing&lt;/strong&gt; — crashed containers are restarted; failed nodes trigger Pod rescheduling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal scaling&lt;/strong&gt; — add replicas with one command or automatically via HPA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rolling updates and rollbacks&lt;/strong&gt; — new versions roll out gradually; one command reverts them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service discovery and load balancing&lt;/strong&gt; — Pods get stable DNS names without static IPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declarative configuration&lt;/strong&gt; — your entire infrastructure lives in version-controlled YAML.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Kubernetes does not replace a CI/CD pipeline. It is the runtime target; tools like Argo CD, Flux, or Jenkins deploy to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cluster Architecture
&lt;/h2&gt;

&lt;p&gt;Understanding the architecture prevents a lot of confusion when things go wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Control Plane
&lt;/h3&gt;

&lt;p&gt;The control plane is the brain of the cluster. In managed services (EKS, GKE, AKS) it is operated for you; in self-managed setups you are responsible for its reliability.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;kube-apiserver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single entry point for all cluster operations. Validates and persists objects to etcd. Every &lt;code&gt;kubectl&lt;/code&gt; command talks to this.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;etcd&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Distributed key-value store. Holds all cluster state. Losing etcd without a backup loses your cluster.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;kube-scheduler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Watches for new Pods with no assigned node and picks the best node based on resource requests, taints, affinity rules, and topology spread.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;kube-controller-manager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs core controllers: ReplicaSet, Deployment, Node, Endpoints, ServiceAccount, and others. Each controller reconciles its resource type.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;cloud-controller-manager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;(Cloud clusters only) Interfaces with cloud provider APIs — creates load balancers, routes, storage volumes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Worker Nodes
&lt;/h3&gt;

&lt;p&gt;Every node that runs workloads must have three components:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;kubelet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent on each node. Receives PodSpec from the API server and instructs the container runtime to start/stop containers. Reports node and Pod status.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;kube-proxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maintains iptables/ipvs rules that implement Service networking — forwarding traffic to the correct Pod IPs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Container runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Actually runs containers. Kubernetes supports any CRI-compatible runtime: containerd (most common), CRI-O, or Docker (via cri-dockerd shim).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; In production, the control plane nodes are typically tainted so user workloads do not schedule on them. Managed services do this automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  How a Pod Gets Scheduled
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;You run &lt;code&gt;kubectl apply -f pod.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kube-apiserver&lt;/code&gt; validates the manifest and stores it in etcd.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kube-scheduler&lt;/code&gt; notices a Pod with no &lt;code&gt;nodeName&lt;/code&gt; and selects a node.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubelet&lt;/code&gt; on that node watches the API server, sees the Pod assigned to it, pulls the image, and starts the container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kube-proxy&lt;/code&gt; updates network rules so the Pod is reachable via its Service.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;
Kubernetes cluster architecture: control plane (API server, etcd, scheduler, controller-manager) communicates with worker nodes (kubelet, kube-proxy, runtime) that host Pods.





&lt;h2&gt;
  
  
  Pods
&lt;/h2&gt;

&lt;p&gt;A Pod is the smallest schedulable unit in Kubernetes. It wraps one or more containers (built from &lt;a href="https://opscanopy.com/learn/guides/docker-for-devops/" rel="noopener noreferrer"&gt;Docker for DevOps&lt;/a&gt; images) that share:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network namespace&lt;/strong&gt; — all containers in a Pod share the same IP address and port space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt; — volumes are declared at the Pod level and mounted into individual containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lifecycle&lt;/strong&gt; — all containers start and stop together with the Pod.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Minimal Pod Manifest
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-pod&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.27&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pod.yaml
kubectl get pods
kubectl describe pod web-pod
kubectl delete pod web-pod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pod Lifecycle
&lt;/h3&gt;

&lt;p&gt;A Pod moves through these phases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Pending&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pod accepted by the cluster; containers not yet started (pulling images, waiting for node).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Running&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At least one container is running or starting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Succeeded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All containers exited with code 0 (typical for Jobs).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Failed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At least one container exited with non-zero code and will not restart.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Unknown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Node communication lost; state cannot be determined.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Multi-Container Patterns
&lt;/h3&gt;

&lt;p&gt;Pods can host multiple containers. The three canonical patterns are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sidecar&lt;/strong&gt; — a helper container that augments the main one (e.g., a log-shipper or a service mesh proxy like Envoy/Istio).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Init container&lt;/strong&gt; — runs to completion &lt;em&gt;before&lt;/em&gt; the main containers start (e.g., database migration, config rendering from a vault).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambassador&lt;/strong&gt; — a proxy container that abstracts external services (e.g., a Redis proxy in front of a remote cluster).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-with-sidecar&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;initContainers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;init-migrate&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;migrate-tool:v1&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./migrate"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--up"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:v2&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;log-shipper&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent/fluent-bit:3.0&lt;/span&gt;
      &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-logs&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/log/app&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-logs&lt;/span&gt;
      &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why You Rarely Create Bare Pods
&lt;/h3&gt;

&lt;p&gt;Bare Pods are not rescheduled if the node they are on fails. You should almost always use a higher-level workload controller (Deployment, StatefulSet, DaemonSet) that manages Pods on your behalf.&lt;/p&gt;

&lt;h2&gt;
  
  
  ReplicaSets and Deployments
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ReplicaSet
&lt;/h3&gt;

&lt;p&gt;A ReplicaSet ensures a specified number of identical Pod replicas are running at any time. It uses a label selector to identify the Pods it owns. If a Pod dies, the ReplicaSet creates a replacement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You almost never create ReplicaSets directly. Deployments create and manage ReplicaSets for you, adding rollout history.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Deployments
&lt;/h3&gt;

&lt;p&gt;A Deployment wraps a ReplicaSet and adds rollout logic: you update the Pod template and the Deployment gradually replaces old Pods with new ones according to the configured strategy.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
&lt;/p&gt;
A Deployment owns a ReplicaSet, which owns and maintains the desired number of identical Pods via a label selector.





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-deployment&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RollingUpdate&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:v2&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;selector.matchLabels&lt;/code&gt; — the Deployment manages any Pod with these labels. Must match &lt;code&gt;template.metadata.labels&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strategy.rollingUpdate.maxUnavailable&lt;/code&gt; — how many Pods can be unavailable during a rollout (absolute or percentage).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strategy.rollingUpdate.maxSurge&lt;/code&gt; — how many extra Pods can be created above the desired count during a rollout.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Apply or update&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml

&lt;span class="c"&gt;# Watch rollout progress&lt;/span&gt;
kubectl rollout status deployment/web-deployment

&lt;span class="c"&gt;# Scale manually&lt;/span&gt;
kubectl scale deployment web-deployment &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5

&lt;span class="c"&gt;# View rollout history&lt;/span&gt;
kubectl rollout &lt;span class="nb"&gt;history &lt;/span&gt;deployment/web-deployment

&lt;span class="c"&gt;# Rollback one version&lt;/span&gt;
kubectl rollout undo deployment/web-deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always set &lt;code&gt;resources.requests&lt;/code&gt; and &lt;code&gt;resources.limits&lt;/code&gt;. Without them, the scheduler cannot make good placement decisions and your Pods get the lowest QoS class.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Services
&lt;/h2&gt;

&lt;p&gt;A Service gives a stable DNS name and IP to a dynamic set of Pods selected by a label selector. Even as Pods are created and destroyed, the Service's IP (&lt;code&gt;ClusterIP&lt;/code&gt;) and DNS name stay constant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Types
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Accessibility&lt;/th&gt;
&lt;th&gt;Typical Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ClusterIP&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;Inside the cluster only&lt;/td&gt;
&lt;td&gt;Internal microservice communication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NodePort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cluster nodes' IPs on a static port (30000–32767)&lt;/td&gt;
&lt;td&gt;Dev/test external access without a cloud LB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LoadBalancer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;External IP via cloud provider's load balancer&lt;/td&gt;
&lt;td&gt;Production external traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ExternalName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DNS alias for an external service&lt;/td&gt;
&lt;td&gt;Pointing in-cluster workloads at external endpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;
Service types and Ingress: Ingress routes HTTP traffic by host/path to Services, while LoadBalancer and NodePort expose Services externally; ClusterIP is internal-only.





&lt;h3&gt;
  
  
  ClusterIP Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-service&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pods in the same namespace can reach this service at &lt;code&gt;web-service:80&lt;/code&gt;; pods in other namespaces use &lt;code&gt;web-service.production.svc.cluster.local:80&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  LoadBalancer Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-lb&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;service.beta.kubernetes.io/aws-load-balancer-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nlb"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How kube-proxy Implements Services
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kube-proxy&lt;/code&gt; watches the API server for Service and Endpoints objects. When a Service is created, it programs iptables rules (or IPVS rules in IPVS mode) on every node so that traffic to the ClusterIP is DNAT'd to one of the healthy Pod IPs in the Endpoints list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc &lt;span class="nt"&gt;-n&lt;/span&gt; production
kubectl describe svc web-service &lt;span class="nt"&gt;-n&lt;/span&gt; production
kubectl get endpoints web-service &lt;span class="nt"&gt;-n&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; A Service with no matching Pods will have an empty Endpoints list and traffic will be silently dropped. Always check &lt;code&gt;kubectl get endpoints&lt;/code&gt; when debugging connectivity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ingress
&lt;/h2&gt;

&lt;p&gt;A Service of type &lt;code&gt;LoadBalancer&lt;/code&gt; creates a separate cloud load balancer per service — expensive at scale. An &lt;strong&gt;Ingress&lt;/strong&gt; is a cluster-wide HTTP(S) router that multiplexes many Services behind a single load balancer using hostname and path rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ingress Controller
&lt;/h3&gt;

&lt;p&gt;An Ingress object is just a configuration resource. You also need an &lt;strong&gt;Ingress controller&lt;/strong&gt; — a Pod that reads Ingress objects and programs a reverse proxy. Common choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NGINX Ingress Controller&lt;/strong&gt; (most widely deployed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Load Balancer Controller&lt;/strong&gt; (for ALB on EKS — see &lt;a href="https://opscanopy.com/learn/guides/aws-for-devops-engineers/" rel="noopener noreferrer"&gt;AWS for DevOps Engineers&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traefik&lt;/strong&gt; (dynamic config, good for smaller clusters)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Istio Gateway&lt;/strong&gt; (service mesh, advanced traffic management)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ingress Manifest with TLS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-ingress&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/rewrite-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/ssl-redirect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app.example.com&lt;/span&gt;
      &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tls-app-example-com&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.example.com&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api&lt;/span&gt;
            &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-service&lt;/span&gt;
                &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-service&lt;/span&gt;
                &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tls.secretName&lt;/code&gt; field references a Kubernetes Secret of type &lt;code&gt;kubernetes.io/tls&lt;/code&gt; that holds the certificate and key. Tools like &lt;strong&gt;cert-manager&lt;/strong&gt; automate TLS certificate provisioning and renewal via Let's Encrypt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get ingress &lt;span class="nt"&gt;-n&lt;/span&gt; production
kubectl describe ingress web-ingress &lt;span class="nt"&gt;-n&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Set &lt;code&gt;ingressClassName&lt;/code&gt; explicitly. Older clusters used annotations (&lt;code&gt;kubernetes.io/ingress.class&lt;/code&gt;), but the &lt;code&gt;ingressClassName&lt;/code&gt; field is the current standard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ConfigMaps
&lt;/h2&gt;

&lt;p&gt;A ConfigMap stores non-sensitive configuration as key-value pairs or files. Pods consume ConfigMaps as environment variables or as mounted files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a ConfigMap
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-config&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info"&lt;/span&gt;
  &lt;span class="na"&gt;MAX_CONNECTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100"&lt;/span&gt;
  &lt;span class="na"&gt;config.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;server:&lt;/span&gt;
      &lt;span class="s"&gt;host: 0.0.0.0&lt;/span&gt;
      &lt;span class="s"&gt;port: 8080&lt;/span&gt;
    &lt;span class="s"&gt;database:&lt;/span&gt;
      &lt;span class="s"&gt;pool_size: 10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; configmap.yaml
kubectl get configmap app-config &lt;span class="nt"&gt;-n&lt;/span&gt; production &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Consuming a ConfigMap as Environment Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:v2&lt;/span&gt;
      &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or inject individual keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LOG_LEVEL&lt;/span&gt;
          &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;configMapKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-config&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LOG_LEVEL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Consuming a ConfigMap as a Mounted File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:v2&lt;/span&gt;
      &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config-volume&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/app&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config-volume&lt;/span&gt;
      &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-config&lt;/span&gt;
        &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config.yaml&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file &lt;code&gt;/etc/app/config.yaml&lt;/code&gt; inside the container will contain the YAML defined in the ConfigMap.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; When you update a ConfigMap that is mounted as a volume, Kubernetes eventually (within ~60 seconds) updates the file in the container. Environment variables injected via &lt;code&gt;envFrom&lt;/code&gt; are &lt;strong&gt;not&lt;/strong&gt; updated — the Pod must be restarted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Secrets
&lt;/h2&gt;

&lt;p&gt;Secrets store sensitive data — passwords, API tokens, TLS certificates, SSH keys. They are similar to ConfigMaps but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Values are &lt;strong&gt;base64-encoded&lt;/strong&gt; (not encrypted by default, but can be with encryption at rest).&lt;/li&gt;
&lt;li&gt;Access is controlled by &lt;strong&gt;RBAC&lt;/strong&gt; — separate from ConfigMap access.&lt;/li&gt;
&lt;li&gt;They integrate with external secret stores (HashiCorp Vault, AWS Secrets Manager) via CSI drivers or operators.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Secret Types
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Opaque&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;Arbitrary key-value data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubernetes.io/tls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TLS certificate and key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubernetes.io/dockerconfigjson&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Docker registry credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubernetes.io/service-account-token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SA tokens (auto-managed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubernetes.io/ssh-auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SSH private keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubernetes.io/basic-auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Username/password pairs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;
ConfigMaps and Secrets inject configuration into Pods as environment variables (envFrom / secretKeyRef) or as mounted files in volumes.





&lt;h3&gt;
  
  
  Creating an Opaque Secret
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-credentials&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YWRtaW4=&lt;/span&gt;        &lt;span class="c1"&gt;# base64("admin")&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c3VwZXJzZWNyZXQ=&lt;/span&gt; &lt;span class="c1"&gt;# base64("supersecret")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Encode a value&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"supersecret"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;

&lt;span class="c"&gt;# Create from literal (easier)&lt;/span&gt;
kubectl create secret generic db-credentials &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;supersecret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# View (values are base64)&lt;/span&gt;
kubectl get secret db-credentials &lt;span class="nt"&gt;-n&lt;/span&gt; production &lt;span class="nt"&gt;-o&lt;/span&gt; yaml

&lt;span class="c"&gt;# Decode a value&lt;/span&gt;
kubectl get secret db-credentials &lt;span class="nt"&gt;-n&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.password}'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Consuming a Secret in a Pod
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:v2&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DB_USERNAME&lt;/span&gt;
          &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-credentials&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DB_PASSWORD&lt;/span&gt;
          &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-credentials&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encryption at Rest and RBAC
&lt;/h3&gt;

&lt;p&gt;By default, Secrets in etcd are stored base64-encoded but not encrypted. Enable encryption at rest via the API server's &lt;code&gt;--encryption-provider-config&lt;/code&gt; flag using an &lt;code&gt;aescbc&lt;/code&gt; or &lt;code&gt;kms&lt;/code&gt; provider.&lt;/p&gt;

&lt;p&gt;Use RBAC to restrict which service accounts can &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, or &lt;code&gt;watch&lt;/code&gt; Secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret-reader&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secrets"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resourceNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db-credentials"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Never commit Secret manifests with real values to version control. Use sealed secrets (Bitnami Sealed Secrets), External Secrets Operator, or a Vault CSI driver to inject secrets at runtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Namespaces
&lt;/h2&gt;

&lt;p&gt;Namespaces provide a mechanism for isolating groups of resources within a single cluster. They are soft multi-tenancy: they scope names, RBAC, and resource quotas, but do not provide strong network isolation by themselves (for that, use NetworkPolicies).&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-in Namespaces
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Namespace&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Default for resources with no namespace specified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kube-system&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes control plane components (CoreDNS, kube-proxy, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kube-public&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publicly readable resources (ConfigMap with cluster info)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kube-node-lease&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Node heartbeat lease objects&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Common Namespace Operations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all namespaces&lt;/span&gt;
kubectl get namespaces

&lt;span class="c"&gt;# Create a namespace&lt;/span&gt;
kubectl create namespace staging

&lt;span class="c"&gt;# Run commands in a specific namespace&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; staging

&lt;span class="c"&gt;# Set a default namespace for your context&lt;/span&gt;
kubectl config set-context &lt;span class="nt"&gt;--current&lt;/span&gt; &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;staging

&lt;span class="c"&gt;# View resources across all namespaces&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;--all-namespaces&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ResourceQuota and LimitRange
&lt;/h3&gt;

&lt;p&gt;Namespaces support &lt;code&gt;ResourceQuota&lt;/code&gt; (cap total resource usage per namespace) and &lt;code&gt;LimitRange&lt;/code&gt; (set default and max limits per container).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ResourceQuota&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;team-quota&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests.cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4"&lt;/span&gt;
    &lt;span class="na"&gt;requests.memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8Gi&lt;/span&gt;
    &lt;span class="na"&gt;limits.cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8"&lt;/span&gt;
    &lt;span class="na"&gt;limits.memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16Gi&lt;/span&gt;
    &lt;span class="na"&gt;count/pods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resource Requests and Limits
&lt;/h2&gt;

&lt;p&gt;Getting resource configuration right is one of the highest-leverage tuning tasks in Kubernetes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Concepts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Request&lt;/strong&gt;: the amount of CPU/memory the scheduler &lt;em&gt;reserves&lt;/em&gt; on a node for this container. The node must have at least this much available for the Pod to be scheduled there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit&lt;/strong&gt;: the maximum CPU/memory the container is allowed to use. Exceeding the CPU limit causes throttling; exceeding the memory limit causes the container to be OOM-killed.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;      &lt;span class="c1"&gt;# 250 millicores = 0.25 CPU core&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CPU is expressed in cores (&lt;code&gt;1&lt;/code&gt;, &lt;code&gt;0.5&lt;/code&gt;) or millicores (&lt;code&gt;500m&lt;/code&gt;). Memory uses binary suffixes (&lt;code&gt;Ki&lt;/code&gt;, &lt;code&gt;Mi&lt;/code&gt;, &lt;code&gt;Gi&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  QoS Classes
&lt;/h3&gt;

&lt;p&gt;Kubernetes assigns one of three Quality of Service classes to every Pod based on its resource configuration:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;QoS Class&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Eviction Priority&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Guaranteed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every container has &lt;code&gt;requests == limits&lt;/code&gt; for both CPU and memory&lt;/td&gt;
&lt;td&gt;Last to be evicted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Burstable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;At least one container has a request or limit, but not Guaranteed&lt;/td&gt;
&lt;td&gt;Middle priority&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BestEffort&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No container has any request or limit&lt;/td&gt;
&lt;td&gt;First to be evicted under pressure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;
CPU/memory request (scheduler reservation) versus limit (hard ceiling), and the three QoS classes that determine eviction order under node pressure.





&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For production workloads, aim for &lt;strong&gt;Guaranteed&lt;/strong&gt; or &lt;strong&gt;Burstable&lt;/strong&gt; QoS. Use &lt;code&gt;kubectl describe pod &amp;lt;name&amp;gt;&lt;/code&gt; and look for &lt;code&gt;QoS Class:&lt;/code&gt; in the output.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setting the Right Numbers
&lt;/h3&gt;

&lt;p&gt;Right-sizing requests and limits requires profiling your actual workload under load. Size CPU/memory requests and limits with the &lt;a href="https://opscanopy.com/kubernetes-resource-calculator/" rel="noopener noreferrer"&gt;Kubernetes Resource Calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Common mistakes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting limits much higher than requests creates "noisy neighbour" risk.&lt;/li&gt;
&lt;li&gt;Setting limits equal to very low requests may cause OOMKills under normal spikes.&lt;/li&gt;
&lt;li&gt;Not setting requests at all results in BestEffort Pods that are first to go under node pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Vertical Pod Autoscaler (VPA) and Horizontal Pod Autoscaler (HPA)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HPA&lt;/strong&gt; scales the &lt;em&gt;number&lt;/em&gt; of replicas based on CPU utilisation, memory, or custom metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPA&lt;/strong&gt; adjusts the &lt;em&gt;requests/limits&lt;/em&gt; of existing containers (requires Pod restarts).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;autoscaling/v2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HorizontalPodAutoscaler&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-hpa&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-deployment&lt;/span&gt;
  &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Resource&lt;/span&gt;
      &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cpu&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Utilization&lt;/span&gt;
          &lt;span class="na"&gt;averageUtilization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Health Probes
&lt;/h2&gt;

&lt;p&gt;Kubernetes uses three probe types to determine container health. Without probes, a container is considered healthy the moment it starts — even if the application inside is not ready yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Probe Types
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Probe&lt;/th&gt;
&lt;th&gt;Kubernetes Action on Failure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Liveness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Restart the container. Use for detecting deadlocks — processes that are running but stuck.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Readiness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Remove the Pod from Service endpoints. Traffic stops flowing to it, but the container is not restarted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Disables liveness and readiness until it succeeds. Use for slow-starting apps to prevent premature liveness restarts.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Probe Mechanisms
&lt;/h3&gt;

&lt;p&gt;Each probe type can use one of three mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;httpGet&lt;/code&gt; — HTTP GET request; success = 2xx/3xx response.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tcpSocket&lt;/code&gt; — TCP connection; success = port is open.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exec&lt;/code&gt; — run a command inside the container; success = exit code 0.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Full Probe Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:v2&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
      &lt;span class="na"&gt;startupProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/healthz&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
      &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/ready&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
        &lt;span class="na"&gt;successThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/healthz&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
        &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
        &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
        &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
        &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key parameters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;initialDelaySeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Seconds after container start before first probe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;periodSeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;How often to probe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failureThreshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Failures before action is taken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;successThreshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Successes needed to transition back to healthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timeoutSeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Seconds to wait for probe response&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always implement separate &lt;code&gt;/healthz&lt;/code&gt; (liveness) and &lt;code&gt;/ready&lt;/code&gt; (readiness) endpoints in your application. The readiness endpoint should check downstream dependencies (database reachability, cache connection); the liveness endpoint should only check the application itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Rollouts and Rollbacks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rolling Update Strategy
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;RollingUpdate&lt;/code&gt; strategy (default for Deployments) replaces old Pods with new ones incrementally. The &lt;code&gt;Recreate&lt;/code&gt; strategy terminates all old Pods before starting new ones — useful when your app cannot run two versions simultaneously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Trigger a rollout by updating the image&lt;/span&gt;
kubectl &lt;span class="nb"&gt;set &lt;/span&gt;image deployment/web-deployment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;web&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp:v3 &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Or edit the manifest and apply&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml

&lt;span class="c"&gt;# Watch the rollout in real time&lt;/span&gt;
kubectl rollout status deployment/web-deployment &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Pause a rollout mid-way (canary-style)&lt;/span&gt;
kubectl rollout pause deployment/web-deployment &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Resume&lt;/span&gt;
kubectl rollout resume deployment/web-deployment &lt;span class="nt"&gt;-n&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rollback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View rollout history with change-cause annotations&lt;/span&gt;
kubectl rollout &lt;span class="nb"&gt;history &lt;/span&gt;deployment/web-deployment &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# View a specific revision&lt;/span&gt;
kubectl rollout &lt;span class="nb"&gt;history &lt;/span&gt;deployment/web-deployment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--revision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Roll back to previous version&lt;/span&gt;
kubectl rollout undo deployment/web-deployment &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Roll back to a specific revision&lt;/span&gt;
kubectl rollout undo deployment/web-deployment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--to-revision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;-n&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Annotate your rollouts with a change-cause so history is readable: add &lt;code&gt;kubernetes.io/change-cause: "bump to v3, adds auth middleware"&lt;/code&gt; to the Deployment's &lt;code&gt;metadata.annotations&lt;/code&gt; before applying.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Deployment Revision History
&lt;/h3&gt;

&lt;p&gt;By default, Kubernetes retains 10 revisions. Control this with &lt;code&gt;spec.revisionHistoryLimit&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;revisionHistoryLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;A Kubernetes cluster generates a rich stream of metrics, logs, and events. Building good visibility requires three layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  Logs
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kubectl logs&lt;/code&gt; reads from the container's stdout/stderr (captured by the container runtime and available via the API server):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Follow logs from a running Pod&lt;/span&gt;
kubectl logs &lt;span class="nt"&gt;-f&lt;/span&gt; deployment/web-deployment &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Show last 100 lines&lt;/span&gt;
kubectl logs web-pod &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Logs from a specific container in a multi-container Pod&lt;/span&gt;
kubectl logs web-pod &lt;span class="nt"&gt;-c&lt;/span&gt; log-shipper &lt;span class="nt"&gt;-n&lt;/span&gt; production

&lt;span class="c"&gt;# Logs from a previous (crashed) container instance&lt;/span&gt;
kubectl logs web-pod &lt;span class="nt"&gt;--previous&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For persistent log aggregation, deploy a log collector DaemonSet (Fluent Bit, Fluentd) that ships container logs to a backend (Elasticsearch, Loki, CloudWatch Logs).&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics and Prometheus
&lt;/h3&gt;

&lt;p&gt;The Kubernetes ecosystem is deeply integrated with &lt;strong&gt;Prometheus&lt;/strong&gt;. The &lt;code&gt;metrics-server&lt;/code&gt; provides basic CPU/memory metrics for HPA and &lt;code&gt;kubectl top&lt;/code&gt;. For full observability, deploy the &lt;strong&gt;kube-prometheus-stack&lt;/strong&gt; (Prometheus + Alertmanager + Grafana) or a managed equivalent.&lt;/p&gt;

&lt;p&gt;Key scrape targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kube-state-metrics&lt;/code&gt; — Deployment replicas, Pod status, resource requests/limits, PVC state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node-exporter&lt;/code&gt; — node-level CPU, memory, disk, network.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cadvisor&lt;/code&gt; — per-container CPU and memory (built into kubelet).&lt;/li&gt;
&lt;li&gt;Your application's &lt;code&gt;/metrics&lt;/code&gt; endpoint (expose Prometheus format).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding Prometheus query syntax is essential for writing alerts and dashboards. Use the &lt;a href="https://opscanopy.com/promql-explainer/" rel="noopener noreferrer"&gt;PromQL Explainer&lt;/a&gt; to translate complex queries into plain English. When building scrape configurations and relabelling rules, the &lt;a href="https://opscanopy.com/prometheus-relabel-tester/" rel="noopener noreferrer"&gt;Prometheus Relabel Tester&lt;/a&gt; lets you test rules against live label sets without touching production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Events
&lt;/h3&gt;

&lt;p&gt;Kubernetes Events are the first place to look when a Pod is not starting correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get events &lt;span class="nt"&gt;-n&lt;/span&gt; production &lt;span class="nt"&gt;--sort-by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'.lastTimestamp'&lt;/span&gt;
kubectl get events &lt;span class="nt"&gt;-n&lt;/span&gt; production &lt;span class="nt"&gt;--field-selector&lt;/span&gt; &lt;span class="nv"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OOMKilled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  kubectl Essentials
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl get pods -n &amp;lt;ns&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List Pods in a namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl get pods -A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List Pods in all namespaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl describe pod &amp;lt;name&amp;gt; -n &amp;lt;ns&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full details, events, conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl logs &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stdout/stderr from the main container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl logs &amp;lt;pod&amp;gt; -c &amp;lt;container&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logs from a specific container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl logs &amp;lt;pod&amp;gt; --previous&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logs from the last crashed container instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl exec -it &amp;lt;pod&amp;gt; -- bash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Interactive shell in a running container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl exec &amp;lt;pod&amp;gt; -- &amp;lt;cmd&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run a one-off command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -f &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declaratively create or update resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl delete -f &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete resources defined in a manifest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl delete pod &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Force-delete a single Pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl scale deployment &amp;lt;name&amp;gt; --replicas=N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scale a Deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl rollout status deployment/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Watch rollout progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl rollout undo deployment/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Roll back to previous version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl rollout history deployment/&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;View revision history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl set image deployment/&amp;lt;name&amp;gt; &amp;lt;c&amp;gt;=&amp;lt;img&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update a container image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl port-forward svc/&amp;lt;name&amp;gt; 8080:80&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forward a local port to a Service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl port-forward pod/&amp;lt;name&amp;gt; 8080:8080&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forward a local port to a Pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl top pods -n &amp;lt;ns&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current CPU/memory usage (requires metrics-server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl top nodes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Node CPU/memory usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl get events -n &amp;lt;ns&amp;gt; --sort-by=.lastTimestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recent cluster events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl explain deployment.spec.strategy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inline API documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl diff -f &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preview what &lt;code&gt;apply&lt;/code&gt; would change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl get all -n &amp;lt;ns&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All common resources in a namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl label pod &amp;lt;name&amp;gt; key=value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add or update a label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl annotate pod &amp;lt;name&amp;gt; key=value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add or update an annotation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl config get-contexts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List available kubeconfig contexts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl config use-context &amp;lt;context&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch cluster context&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Install &lt;code&gt;kubectl&lt;/code&gt; plugins via &lt;code&gt;krew&lt;/code&gt; (the kubectl plugin manager). Useful plugins include &lt;code&gt;kubectl-neat&lt;/code&gt; (clean up &lt;code&gt;get -o yaml&lt;/code&gt; output), &lt;code&gt;kubectl-ctx&lt;/code&gt; and &lt;code&gt;kubectl-ns&lt;/code&gt; (fast context/namespace switching), and &lt;code&gt;kubectl-tree&lt;/code&gt; (visualise owner references).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CrashLoopBackOff
&lt;/h3&gt;

&lt;p&gt;A container keeps crashing and Kubernetes keeps restarting it with exponential backoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See the current state and restart count&lt;/span&gt;
kubectl get pod &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;

&lt;span class="c"&gt;# Check events for the Pod&lt;/span&gt;
kubectl describe pod &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;

&lt;span class="c"&gt;# Check logs from the current or previous instance&lt;/span&gt;
kubectl logs &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;
kubectl logs &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt; &lt;span class="nt"&gt;--previous&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common causes:&lt;/strong&gt; application throws an unhandled exception at startup; missing environment variable or config file; entrypoint command is wrong; dependency (database, cache) not reachable.&lt;/p&gt;

&lt;h3&gt;
  
  
  ImagePullBackOff / ErrImagePull
&lt;/h3&gt;

&lt;p&gt;The container image cannot be pulled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe pod &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;
&lt;span class="c"&gt;# Look for events like: Failed to pull image ... unauthorized&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common causes:&lt;/strong&gt; image name or tag is wrong; registry is private and no &lt;code&gt;imagePullSecret&lt;/code&gt; is configured; credentials in the pull secret have expired; rate-limit on Docker Hub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix a missing imagePullSecret:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create secret docker-registry regcred &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker-server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry.example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker-username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;user&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker-password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;token&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reference it in the Pod spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;imagePullSecrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;regcred&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pending Pods
&lt;/h3&gt;

&lt;p&gt;A Pod stays in &lt;code&gt;Pending&lt;/code&gt; and never starts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe pod &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;
&lt;span class="c"&gt;# Look for events under "Events:" — scheduler messages explain why&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common causes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Insufficient resources&lt;/strong&gt; — no node has enough CPU or memory to satisfy the requests. Check &lt;code&gt;kubectl top nodes&lt;/code&gt; and &lt;code&gt;kubectl describe nodes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taint/toleration mismatch&lt;/strong&gt; — the target nodes are tainted but the Pod has no matching toleration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NodeSelector or affinity&lt;/strong&gt; — the Pod requires specific labels on the node that no current node has.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PVC not bound&lt;/strong&gt; — a &lt;code&gt;PersistentVolumeClaim&lt;/code&gt; is still &lt;code&gt;Pending&lt;/code&gt; (waiting for a PV).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OOMKilled
&lt;/h3&gt;

&lt;p&gt;A container was killed by the kernel OOM killer because it exceeded its memory limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pod &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;
&lt;span class="c"&gt;# Look for: OOMKilled in the REASON column&lt;/span&gt;

kubectl describe pod &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt;
&lt;span class="c"&gt;# Look for: Last State: Terminated Reason: OOMKilled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Increase the memory limit (or request, to get a Guaranteed QoS). Profile the application's actual memory use under load. Use the &lt;a href="https://opscanopy.com/kubernetes-resource-calculator/" rel="noopener noreferrer"&gt;Kubernetes Resource Calculator&lt;/a&gt; to size requests and limits based on observed usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging Toolkit
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;kubectl logs&lt;/code&gt; is not enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Open a shell in the running container&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;pod&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; sh

&lt;span class="c"&gt;# Run a temporary debug Pod on a specific node&lt;/span&gt;
kubectl debug node/&amp;lt;node-name&amp;gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu:22.04

&lt;span class="c"&gt;# Attach an ephemeral debug container to a running Pod (K8s 1.23+)&lt;/span&gt;
kubectl debug &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;pod&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;ns&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;busybox:1.36 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;container-name&amp;gt;

&lt;span class="c"&gt;# Copy a file from a Pod to local&lt;/span&gt;
kubectl &lt;span class="nb"&gt;cp&lt;/span&gt; &amp;lt;ns&amp;gt;/&amp;lt;pod&amp;gt;:/path/to/file ./local-file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Kubernetes Interview Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: What is the role of the etcd in a Kubernetes cluster?&lt;/strong&gt;&lt;br&gt;
A: etcd is the cluster's source of truth — a distributed, consistent key-value store where all cluster state (objects, configs, secrets) is persisted. If etcd is lost without a backup, the cluster's configuration is lost. The API server is the only component that reads and writes etcd directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How does a Kubernetes Service discover which Pods to send traffic to?&lt;/strong&gt;&lt;br&gt;
A: Services use label selectors to identify target Pods. The Endpoints controller (or EndpointSlice controller in newer clusters) watches for Pods matching the selector and maintains an Endpoints/EndpointSlice object. &lt;code&gt;kube-proxy&lt;/code&gt; reads these to program iptables/IPVS forwarding rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is the difference between a StatefulSet and a Deployment?&lt;/strong&gt;&lt;br&gt;
A: A Deployment is for stateless workloads — Pods are interchangeable. A StatefulSet is for stateful applications (databases, queues): it gives each Pod a stable, ordered hostname (&lt;code&gt;pod-0&lt;/code&gt;, &lt;code&gt;pod-1&lt;/code&gt;), stable storage via PVCs that survive Pod rescheduling, and ordered, graceful start/stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is a DaemonSet?&lt;/strong&gt;&lt;br&gt;
A: A DaemonSet ensures that one copy of a Pod runs on every node (or a subset via nodeSelector/tolerations). Common uses: log collectors (Fluent Bit), monitoring agents (node-exporter), CNI plugins, security agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How does Kubernetes handle persistent storage?&lt;/strong&gt;&lt;br&gt;
A: Via the PersistentVolume (PV) / PersistentVolumeClaim (PVC) abstraction. A PVC is a user's request for storage (size, access mode). Kubernetes binds it to a matching PV (a piece of actual storage — EBS volume, NFS share, etc.) either statically or dynamically via a StorageClass and a CSI driver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is a Kubernetes Namespace and when should you use multiple namespaces?&lt;/strong&gt;&lt;br&gt;
A: A Namespace is a virtual cluster within a cluster. Use separate namespaces for teams, environments (dev/staging/production on the same cluster), or application boundaries. Apply ResourceQuotas and RBAC per namespace to enforce isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is a Taint and Toleration?&lt;/strong&gt;&lt;br&gt;
A: Taints are set on nodes to repel Pods. &lt;code&gt;kubectl taint nodes node1 key=value:NoSchedule&lt;/code&gt; prevents any Pod without a matching Toleration from being scheduled on that node. Used to dedicate nodes for specific workloads (GPU nodes, spot nodes).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What happens when you run &lt;code&gt;kubectl apply -f deployment.yaml&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
A: kubectl sends the manifest to the API server, which validates it and merges it with the existing object (three-way merge using the &lt;code&gt;last-applied-configuration&lt;/code&gt; annotation). If the Deployment's Pod template changed, the Deployment controller creates a new ReplicaSet and performs a rolling update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is the difference between &lt;code&gt;kubectl apply&lt;/code&gt; and &lt;code&gt;kubectl create&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
A: &lt;code&gt;create&lt;/code&gt; is imperative — it fails if the object already exists. &lt;code&gt;apply&lt;/code&gt; is declarative — it creates the object if missing or updates it if it exists, using a three-way merge. Prefer &lt;code&gt;apply&lt;/code&gt; for GitOps and CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What is a Kubernetes Job vs a CronJob?&lt;/strong&gt;&lt;br&gt;
A: A Job creates one or more Pods and tracks their completion. It retries failed Pods until the desired completions count is reached, then the Job is marked complete. A CronJob schedules Jobs on a cron expression, creating a new Job object on each trigger.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Prefer a day-by-day path? This is covered in &lt;a href="https://opscanopy.com/mission-90/" rel="noopener noreferrer"&gt;&lt;strong&gt;Mission 90 Days 66–74&lt;/strong&gt;&lt;/a&gt; — a free 90-day guided DevOps program with browser terminal missions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Coming from containers? See &lt;a href="https://opscanopy.com/learn/guides/docker-for-devops/" rel="noopener noreferrer"&gt;Docker for DevOps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Running on AWS (EKS)? See &lt;a href="https://opscanopy.com/learn/guides/aws-for-devops-engineers/" rel="noopener noreferrer"&gt;AWS for DevOps Engineers&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Docker Interview Prep: Scenario-Based Questions &amp; Answers for DevOps</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Mon, 17 Aug 2026 15:36:01 +0000</pubDate>
      <link>https://dev.to/opscanopy/docker-interview-prep-scenario-based-questions-answers-for-devops-4led</link>
      <guid>https://dev.to/opscanopy/docker-interview-prep-scenario-based-questions-answers-for-devops-4led</guid>
      <description>&lt;p&gt;This guide is built for DevOps interview prep. It covers the ten core Docker topics that come up repeatedly — from why containers exist through to a production-grade troubleshooting playbook — using scenario-based questions, clear answers, worked examples, and the gotchas interviewers love to probe. Every command is real and runnable on Ubuntu 24.04 with a current Docker Engine. Before diving in, bookmark the &lt;a href="https://opscanopy.com/learn/roadmaps/docker/" rel="noopener noreferrer"&gt;Docker roadmap&lt;/a&gt; to see how these topics fit into a full learning path.&lt;/p&gt;

&lt;p&gt;Study the concepts first in &lt;a href="https://opscanopy.com/learn/guides/docker-for-devops/" rel="noopener noreferrer"&gt;Docker for DevOps&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Containers Exist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Interview scenario:&lt;/strong&gt; "You shipped a React app and it works on your machine but breaks in production. How do you explain what went wrong, and what's the architectural fix?"&lt;/p&gt;

&lt;p&gt;Your laptop has Node 20, a specific OpenSSL version, certain environment variables, and a particular Ubuntu setup. The production server has Node 18, a different libc, and missing env vars. Same code, different behaviour. The bug is not in your code — it is in the &lt;em&gt;environment&lt;/em&gt; around your code.&lt;/p&gt;

&lt;p&gt;Traditionally, teams fixed this with long setup documents, install scripts, and "please match these exact versions" checklists. It never fully worked because the operating system, system libraries, and dependencies drifted between every developer's machine and every server. Containers solve this by packaging your app &lt;strong&gt;together with its entire runtime environment&lt;/strong&gt; — the right Node version, libraries, files, and config — into one portable unit that runs identically everywhere.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Think of a shipping container. Before standardised containers, dock workers loaded ships piece by piece — barrels, boxes, sacks — and every port handled it differently, slowly, and unreliably. The steel shipping container changed the world: one standard box, the same shape everywhere, that any crane, truck, or ship can handle without caring what's inside. Docker does the same for software.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Virtual Machines vs Containers
&lt;/h3&gt;

&lt;p&gt;Before containers, the common way to isolate apps was the Virtual Machine (VM). A VM emulates an entire computer — including a full guest operating system — on top of a hypervisor. That gives strong isolation, but it is heavy: every VM carries its own multi-gigabyte OS.&lt;/p&gt;

&lt;p&gt;Containers take a smarter approach. They &lt;strong&gt;share the host's OS kernel&lt;/strong&gt; and only package the app plus its dependencies, making them tiny and fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture comparison:&lt;/strong&gt; In a VM setup, each application runs inside its own Guest OS on a Hypervisor, all sitting on Host OS + Hardware. Every VM is gigabytes in size. In a container setup, applications each have their own bins/libs but share a single Docker Engine on top of a shared Host OS Kernel. No guest OS means containers are megabytes, not gigabytes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Virtual Machine&lt;/th&gt;
&lt;th&gt;Container&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Boot time&lt;/td&gt;
&lt;td&gt;Minutes (full OS boot)&lt;/td&gt;
&lt;td&gt;Seconds or less&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size&lt;/td&gt;
&lt;td&gt;Gigabytes (includes guest OS)&lt;/td&gt;
&lt;td&gt;Megabytes (app + deps only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolation&lt;/td&gt;
&lt;td&gt;Strong — full hardware-level isolation via hypervisor&lt;/td&gt;
&lt;td&gt;Process-level isolation via kernel namespaces &amp;amp; cgroups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource use&lt;/td&gt;
&lt;td&gt;Heavy — each VM runs its own OS&lt;/td&gt;
&lt;td&gt;Lightweight — shares host kernel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS kernel&lt;/td&gt;
&lt;td&gt;Each VM has its own kernel&lt;/td&gt;
&lt;td&gt;All containers share the host kernel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portability&lt;/td&gt;
&lt;td&gt;Portable but bulky to move&lt;/td&gt;
&lt;td&gt;Highly portable — small images, run anywhere Docker runs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A team running 10 microservices as VMs would need 10 guest operating systems eating ~20 GB of RAM with minutes to spin up. As containers on a single host, those same 10 services share one kernel, boot in seconds, and fit comfortably in a fraction of the memory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What Docker Actually Solves
&lt;/h3&gt;

&lt;p&gt;Docker is not the same thing as "containers" — Linux had the underlying technology (namespaces and cgroups) for years. What Docker did was make containers &lt;strong&gt;easy&lt;/strong&gt;: a simple way to define an environment (the Dockerfile), a standard format to package it (the image), a registry to share it (Docker Hub), and a clean CLI to run it. Consistency from laptop to CI to production, fast startup, and efficient resource use — that is the Docker promise.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; "What's the difference between a VM and a container?" gets asked in almost every DevOps interview. Nail the one-liner: &lt;em&gt;A VM virtualizes hardware and runs a full guest OS on a hypervisor; a container virtualizes the OS and shares the host kernel, packaging only the app and its dependencies.&lt;/em&gt; Then add the consequences — containers are smaller, boot faster, and use fewer resources, while VMs give stronger isolation. Bonus points: they are complementary — you often run containers &lt;em&gt;inside&lt;/em&gt; VMs in the cloud.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Core Concepts: Images, Containers, Registry, and the Engine
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Images vs Containers
&lt;/h3&gt;

&lt;p&gt;This is the single most important distinction in Docker, and it maps perfectly onto something any developer already knows: &lt;strong&gt;class vs object&lt;/strong&gt;. An &lt;strong&gt;image&lt;/strong&gt; is a read-only blueprint — like a class definition. A &lt;strong&gt;container&lt;/strong&gt; is a running instance of that image — like an object created with &lt;code&gt;new&lt;/code&gt;. From one image you can spin up many containers, just like one class can produce many objects, each with its own state.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; An image is the recipe; a container is the actual dish you cooked from it. One recipe, many plates. The recipe never changes when you cook — and your image stays read-only while each container runs its own live copy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Container&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;Read-only template / blueprint&lt;/td&gt;
&lt;td&gt;Running instance of an image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Immutable, static&lt;/td&gt;
&lt;td&gt;Live, has writable layer &amp;amp; runtime state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analogy&lt;/td&gt;
&lt;td&gt;Class / recipe&lt;/td&gt;
&lt;td&gt;Object / cooked dish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle&lt;/td&gt;
&lt;td&gt;Built once, stored, reused&lt;/td&gt;
&lt;td&gt;Created, started, stopped, removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quantity&lt;/td&gt;
&lt;td&gt;One image&lt;/td&gt;
&lt;td&gt;Many containers from that one image&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
One immutable image produces many independent containers, each adding its own ephemeral writable layer on top.





&lt;h3&gt;
  
  
  The Docker Engine Model
&lt;/h3&gt;

&lt;p&gt;"Docker" is actually several pieces working together. Understanding this chain is valuable in interviews.&lt;/p&gt;

&lt;p&gt;The flow is: &lt;strong&gt;CLI Client&lt;/strong&gt; → (REST API) → &lt;strong&gt;Docker Daemon (dockerd)&lt;/strong&gt; → (pull/push) → &lt;strong&gt;Registry (Docker Hub / ECR)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLI Client&lt;/strong&gt; — the &lt;code&gt;docker&lt;/code&gt; command you type. It is a thin client that sends requests to the daemon over a REST API (Unix socket). It does no container work itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Daemon (dockerd)&lt;/strong&gt; — the background service that does the real work: building images, managing containers, networks, and volumes. It delegates container execution to containerd/runc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images&lt;/strong&gt; — read-only templates stored locally by the daemon. If a requested image is absent, the daemon pulls it from the registry. Each image is a stack of layers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers&lt;/strong&gt; — running instances the daemon creates from images by adding a writable layer, then launching the process via containerd → runc with kernel namespaces and cgroups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registry&lt;/strong&gt; — remote image store (Docker Hub, ECR, GHCR). The daemon &lt;code&gt;pull&lt;/code&gt;s images down when missing and &lt;code&gt;push&lt;/code&gt;es built images up to share.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Image Layers and the Union Filesystem
&lt;/h3&gt;

&lt;p&gt;A Docker image is not one solid blob — it is built from &lt;strong&gt;stacked layers&lt;/strong&gt;. Each instruction in a Dockerfile (install a package, copy files, set config) creates a new read-only layer on top of the previous one. Docker uses a &lt;strong&gt;union filesystem&lt;/strong&gt; to merge these layers into a single view that the container sees as one normal filesystem.&lt;/p&gt;

&lt;p&gt;Two big wins come from this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt; — if a layer hasn't changed, Docker reuses the cached version instead of rebuilding it, so builds are fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharing&lt;/strong&gt; — if ten images all start &lt;code&gt;FROM ubuntu:24.04&lt;/code&gt;, that base layer is stored on disk only once and shared between them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When a container runs, Docker adds a thin &lt;strong&gt;writable layer&lt;/strong&gt; on top — all runtime changes live there, and the underlying image layers stay untouched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer stack for a Node app (bottom to top):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:20-alpine        ← base layer (read-only, cached)
COPY package.json ./       ← layer: package.json
RUN npm ci                 ← layer: node_modules (slow, cached when deps unchanged)
COPY . .                   ← layer: app source (changes often)
CMD ["node","server.js"]   ← metadata: start command
                           ← writable container layer (top, ephemeral)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you change a layer, Docker must rebuild that layer &lt;strong&gt;and every layer above it&lt;/strong&gt;. Layers below it stay cached. This is why dependencies go early and source goes last.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Order your Dockerfile from least-changing to most-changing. Put &lt;code&gt;COPY package.json&lt;/code&gt; and &lt;code&gt;npm install&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; &lt;code&gt;COPY . .&lt;/code&gt; — that way, changing your source code doesn't bust the cached dependency layer, and rebuilds stay fast.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Registry and Docker Hub
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;registry&lt;/strong&gt; is a storage and distribution system for images — think of it as "GitHub for Docker images." &lt;strong&gt;Docker Hub&lt;/strong&gt; is the default public registry, hosting official images like &lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;node&lt;/code&gt;, &lt;code&gt;postgres&lt;/code&gt;, and &lt;code&gt;ubuntu&lt;/code&gt;. You &lt;em&gt;pull&lt;/em&gt; images down to run them and &lt;em&gt;push&lt;/em&gt; your own images up to share them. Companies usually run private registries too (AWS ECR, GitHub Container Registry, Harbor) for internal images.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; When you run &lt;code&gt;docker pull nginx&lt;/code&gt;, Docker contacts Docker Hub, finds the official &lt;code&gt;nginx&lt;/code&gt; image, and downloads it layer by layer. Each layer is pulled separately — and any layers already present from a previous pull are skipped. That is the layer-sharing system saving bandwidth and disk in real time.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run nginx
&lt;span class="c"&gt;# CLI (client) ── REST API ──&amp;gt; dockerd (daemon)&lt;/span&gt;
&lt;span class="c"&gt;#                                 │&lt;/span&gt;
&lt;span class="c"&gt;#                  pulls image from registry if missing&lt;/span&gt;
&lt;span class="c"&gt;#                                 │&lt;/span&gt;
&lt;span class="c"&gt;#                              containerd ──&amp;gt; runc ──&amp;gt; container process&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; "Walk me through what happens when you run &lt;code&gt;docker run nginx&lt;/code&gt;." Strong answer: (1) the CLI sends the request to the Docker daemon via the API; (2) the daemon checks for the &lt;code&gt;nginx&lt;/code&gt; image locally; (3) if missing, it pulls the image layers from the registry; (4) it creates a new container by stacking a writable layer on the image; (5) it hands off to containerd/runc, which sets up namespaces and cgroups; (6) the container's main process starts. Mentioning the client–daemon split and containerd/runc shows real depth.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Essential Docker CLI
&lt;/h2&gt;

&lt;p&gt;These are the commands you will type every single day. Each one below has a real example and a clear use case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container lifecycle:&lt;/strong&gt; Created → Running → Stopped → Removed, driven by &lt;code&gt;docker create&lt;/code&gt; → &lt;code&gt;docker start&lt;/code&gt;/&lt;code&gt;run&lt;/code&gt; → &lt;code&gt;docker stop&lt;/code&gt; → &lt;code&gt;docker rm&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker run — create and start a container
&lt;/h3&gt;

&lt;p&gt;The workhorse. Key flags: &lt;code&gt;-d&lt;/code&gt; (detached/background), &lt;code&gt;-p host:container&lt;/code&gt; (publish a port), &lt;code&gt;--name&lt;/code&gt; (friendly name), &lt;code&gt;-it&lt;/code&gt; (interactive terminal), &lt;code&gt;--rm&lt;/code&gt; (auto-delete on exit), &lt;code&gt;-e&lt;/code&gt; (environment variable), &lt;code&gt;-v&lt;/code&gt; (mount a volume).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; web nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run nginx in the background, mapping host port 8080 to container port 80. Open &lt;code&gt;http://localhost:8080&lt;/code&gt; to see it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; ubuntu:24.04 bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get an interactive shell inside a throwaway Ubuntu container that auto-deletes the moment you exit — perfect for quick experiments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; db &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret &lt;span class="nt"&gt;-v&lt;/span&gt; pgdata:/var/lib/postgresql/data &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 postgres:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Postgres with an env var for the password and a named volume &lt;code&gt;pgdata&lt;/code&gt; so your database survives even after the container is removed.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker ps — list containers
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker ps&lt;/code&gt; shows only running containers; &lt;code&gt;docker ps -a&lt;/code&gt; shows all containers including stopped/exited ones — essential for debugging why something died.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker images — list local images
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See every image stored locally, with its repository, tag, image ID, and size.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker pull — download an image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull node:20-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grab a specific image from the registry ahead of time, so the first &lt;code&gt;docker run&lt;/code&gt; doesn't have to wait on the download.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker exec — run a command in a running container
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; web bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jump inside a &lt;em&gt;running&lt;/em&gt; container to inspect files, check config, or debug live. &lt;code&gt;-it&lt;/code&gt; gives you an interactive shell. (If the image lacks bash, use &lt;code&gt;sh&lt;/code&gt;.)&lt;/p&gt;

&lt;h3&gt;
  
  
  docker logs — view container output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stream the logs (stdout/stderr) of the &lt;code&gt;web&lt;/code&gt; container live with &lt;code&gt;-f&lt;/code&gt; (follow) — your go-to for watching what an app is doing in real time.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker stop / start — control lifecycle
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop web
docker start web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gracefully stop a running container (it gets a chance to shut down cleanly) and later start it back up without recreating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker rm / rmi — remove containers and images
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm &lt;/span&gt;web
docker rmi nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker rm&lt;/code&gt; deletes a stopped container; &lt;code&gt;docker rmi&lt;/code&gt; deletes an image you no longer need to reclaim disk space.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker build — build an image from a Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:1.0 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build an image named &lt;code&gt;myapp&lt;/code&gt; tagged &lt;code&gt;1.0&lt;/code&gt; from the Dockerfile in the current directory (the &lt;code&gt;.&lt;/code&gt; is the build context).&lt;/p&gt;

&lt;h3&gt;
  
  
  docker tag — give an image another name/tag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker tag myapp:1.0 pushkar/myapp:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-tag your local image with your Docker Hub username (and/or registry) so it's ready to push to the right repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker push — upload an image to a registry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker login
docker push pushkar/myapp:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;docker login&lt;/code&gt;, push your tagged image to Docker Hub so teammates or your servers can pull and run it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Most-used form&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;run&lt;/td&gt;
&lt;td&gt;Create + start a container&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker run -d -p 8080:80 --name web nginx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ps&lt;/td&gt;
&lt;td&gt;List containers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker ps -a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;images&lt;/td&gt;
&lt;td&gt;List local images&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker images&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pull&lt;/td&gt;
&lt;td&gt;Download an image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker pull node:20-alpine&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exec&lt;/td&gt;
&lt;td&gt;Run a command in a live container&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker exec -it web bash&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;logs&lt;/td&gt;
&lt;td&gt;View container output&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker logs -f web&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stop / start&lt;/td&gt;
&lt;td&gt;Control a container's lifecycle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker stop web&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rm / rmi&lt;/td&gt;
&lt;td&gt;Remove container / image&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker rm web&lt;/code&gt; · &lt;code&gt;docker rmi nginx&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;build&lt;/td&gt;
&lt;td&gt;Build image from Dockerfile&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker build -t myapp:1.0 .&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tag&lt;/td&gt;
&lt;td&gt;Re-name/tag an image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker tag myapp:1.0 pushkar/myapp:1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;Upload image to registry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker push pushkar/myapp:1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;docker run&lt;/code&gt; is really three steps in one: &lt;strong&gt;pull&lt;/strong&gt; (if the image isn't local) + &lt;strong&gt;create&lt;/strong&gt; (build the container from the image) + &lt;strong&gt;start&lt;/strong&gt; (launch its main process). &lt;code&gt;docker create&lt;/code&gt; + &lt;code&gt;docker start&lt;/code&gt; do separately what &lt;code&gt;run&lt;/code&gt; does together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;code&gt;docker rm -f&lt;/code&gt; kills and removes a running container with no graceful shutdown — fine for test junk, dangerous for anything with unsaved state. &lt;code&gt;docker system prune -a&lt;/code&gt; deletes all stopped containers, unused networks, and &lt;em&gt;all&lt;/em&gt; images not currently in use. Read what it's about to delete before you confirm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; A classic gotcha: "What's the difference between &lt;code&gt;docker stop&lt;/code&gt; and &lt;code&gt;docker kill&lt;/code&gt;?" — &lt;code&gt;stop&lt;/code&gt; sends &lt;code&gt;SIGTERM&lt;/code&gt; first (graceful, with a grace period) then &lt;code&gt;SIGKILL&lt;/code&gt;; &lt;code&gt;kill&lt;/code&gt; sends &lt;code&gt;SIGKILL&lt;/code&gt; immediately. Another favourite: "Difference between &lt;code&gt;EXPOSE&lt;/code&gt; in a Dockerfile and &lt;code&gt;-p&lt;/code&gt; at runtime?" — &lt;code&gt;EXPOSE&lt;/code&gt; only documents the port; &lt;code&gt;-p&lt;/code&gt; actually publishes it to the host.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Dockerfile Deep-Dive
&lt;/h2&gt;

&lt;p&gt;A Dockerfile is a recipe. Each line is one instruction that tells Docker how to assemble your image, layer by layer. Think of it like your build pipeline written as plain text: install the runtime, copy your code, install deps, define how the app starts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A Dockerfile is a recipe card and the image is the prepped meal kit. The &lt;em&gt;container&lt;/em&gt; is when you actually cook and eat it. You can build the kit once and cook it on a thousand machines, and it tastes identical every time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  FROM — the base layer
&lt;/h3&gt;

&lt;p&gt;Every Dockerfile starts with &lt;code&gt;FROM&lt;/code&gt;. It sets the base image you build on top of. Pin a specific version tag; never rely on &lt;code&gt;latest&lt;/code&gt; in production because it silently changes under you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;node:20&lt;/code&gt; is the version and &lt;code&gt;-alpine&lt;/code&gt; is the variant (a tiny Linux distro, ~5 MB base).&lt;/p&gt;

&lt;h3&gt;
  
  
  RUN — execute commands at build time
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RUN&lt;/code&gt; runs a command &lt;em&gt;while building the image&lt;/em&gt; and bakes the result into a new layer. Use it to install packages, create directories, or run build steps. Each &lt;code&gt;RUN&lt;/code&gt; creates a layer, so chain related commands with &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; to keep layer count down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; python3 make g++ &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; some-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  COPY — bring files into the image
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;COPY&lt;/code&gt; copies files/folders from your build context (your project directory) into the image. This is how your source code gets in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ADD — COPY's older, fancier cousin
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ADD&lt;/code&gt; does everything &lt;code&gt;COPY&lt;/code&gt; does, plus two extras: it can fetch a remote URL, and it auto-extracts local tar archives. Those extras make it less predictable, so the rule of thumb is: &lt;strong&gt;use COPY unless you specifically need ADD's tar extraction&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;th&gt;COPY&lt;/th&gt;
&lt;th&gt;ADD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copy local files/dirs&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-extract local &lt;code&gt;.tar&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Download from URL&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (discouraged)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Predictable / explicit&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (magic behaviour)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended default&lt;/td&gt;
&lt;td&gt;Use this&lt;/td&gt;
&lt;td&gt;Only for tar extraction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  WORKDIR — set the working directory
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;WORKDIR&lt;/code&gt; sets the current directory for every following instruction (&lt;code&gt;RUN&lt;/code&gt;, &lt;code&gt;CMD&lt;/code&gt;, &lt;code&gt;COPY&lt;/code&gt;, etc.) and creates it if it doesn't exist. Always use it instead of &lt;code&gt;RUN cd /app&lt;/code&gt; — &lt;code&gt;cd&lt;/code&gt; doesn't persist across layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CMD vs ENTRYPOINT — how the container starts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Interview scenario:&lt;/strong&gt; "What's the difference between CMD and ENTRYPOINT?" This is a guaranteed question.&lt;/p&gt;

&lt;p&gt;Both define what runs when the container starts, but they play different roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ENTRYPOINT&lt;/strong&gt; is the fixed command — the thing this container &lt;em&gt;is&lt;/em&gt;. It needs &lt;code&gt;--entrypoint&lt;/code&gt; to override at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CMD&lt;/strong&gt; provides default arguments (or a default command) that are &lt;em&gt;easily overridden&lt;/em&gt; at &lt;code&gt;docker run&lt;/code&gt; time. When both are present, CMD's values are appended as arguments to ENTRYPOINT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Exec form vs shell form:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exec form&lt;/strong&gt; (JSON array): &lt;code&gt;CMD ["node", "server.js"]&lt;/code&gt; — runs the binary directly, no shell. This is the correct form: signals like &lt;code&gt;SIGTERM&lt;/code&gt; reach your process so it can shut down cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shell form&lt;/strong&gt;: &lt;code&gt;CMD node server.js&lt;/code&gt; — wraps the command in &lt;code&gt;/bin/sh -c&lt;/code&gt;. The shell becomes PID 1 and may swallow signals, so your app doesn't get the stop signal. Avoid for the main process.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# They combine: final command = ENTRYPOINT + CMD&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["server.js"]&lt;/span&gt;
&lt;span class="c"&gt;# docker run myimg            -&amp;gt; node server.js&lt;/span&gt;
&lt;span class="c"&gt;# docker run myimg other.js   -&amp;gt; node other.js   (CMD overridden)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;CMD&lt;/th&gt;
&lt;th&gt;ENTRYPOINT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Purpose&lt;/td&gt;
&lt;td&gt;Default command / args&lt;/td&gt;
&lt;td&gt;Fixed executable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overridable at &lt;code&gt;docker run&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes — just pass new args&lt;/td&gt;
&lt;td&gt;No (needs &lt;code&gt;--entrypoint&lt;/code&gt; flag)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When both exist&lt;/td&gt;
&lt;td&gt;Becomes arguments to ENTRYPOINT&lt;/td&gt;
&lt;td&gt;Stays as the executable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;App that users may re-run with different args&lt;/td&gt;
&lt;td&gt;Wrapper / tool where the binary is fixed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can appear multiple times&lt;/td&gt;
&lt;td&gt;Only last one wins&lt;/td&gt;
&lt;td&gt;Only last one wins&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;
ENTRYPOINT locks in the executable; CMD provides the default arguments that a caller can replace without touching the entrypoint.





&lt;h3&gt;
  
  
  EXPOSE — document the port
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;EXPOSE&lt;/code&gt; documents which port the container listens on. It does &lt;strong&gt;not&lt;/strong&gt; publish the port — it is metadata for humans and tools. You still need &lt;code&gt;docker run -p 3000:3000&lt;/code&gt; to actually map it to the host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ENV vs ARG — variables
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ENV&lt;/code&gt; sets environment variables that persist &lt;em&gt;inside the running container&lt;/em&gt; (your app can read them). &lt;code&gt;ARG&lt;/code&gt; sets build-time variables that exist only &lt;em&gt;during the build&lt;/em&gt; and are gone at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_VERSION=20&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="c"&gt;# Pass ARG at build: docker build --build-arg NODE_VERSION=20 .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;ARG&lt;/th&gt;
&lt;th&gt;ENV&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Available during build&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Available in running container&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set from CLI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--build-arg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-e&lt;/code&gt; at run / Dockerfile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Good for&lt;/td&gt;
&lt;td&gt;Versions, build flags&lt;/td&gt;
&lt;td&gt;App config, NODE_ENV, ports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visible in image history&lt;/td&gt;
&lt;td&gt;Yes (don't put secrets)&lt;/td&gt;
&lt;td&gt;Yes (don't put secrets)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Layer caching — why instruction order matters
&lt;/h3&gt;

&lt;p&gt;Every instruction creates a cached layer. On rebuild, Docker reuses a layer if nothing it depends on changed. The moment one layer is invalidated, &lt;em&gt;every layer after it&lt;/em&gt; is rebuilt too. The classic Node mistake is copying everything first and &lt;em&gt;then&lt;/em&gt; installing dependencies — every source change forces &lt;code&gt;npm install&lt;/code&gt; to re-run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# GOOD: deps cached separately from source&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .          # changes often, but npm ci layer stays cached&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put rarely-changing things (dependencies) early, frequently-changing things (source code) late.&lt;/p&gt;

&lt;h3&gt;
  
  
  A realistic Node app Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;

&lt;span class="c"&gt;# 1. Set the working directory&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# 2. Copy only manifests first (cache-friendly)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;

&lt;span class="c"&gt;# 3. Install exact, reproducible deps&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="c"&gt;# 4. Now copy the rest of the source&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# 5. Build the app (e.g. React / Vite / Next build)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# 6. Runtime config&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="c"&gt;# 7. Fixed binary + default arg&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Answer &lt;code&gt;CMD vs ENTRYPOINT&lt;/code&gt; crisply: ENTRYPOINT is the fixed executable; CMD supplies default arguments the user can override at &lt;code&gt;docker run&lt;/code&gt;. If both are set, CMD is appended to ENTRYPOINT. Mention that exec form (&lt;code&gt;["node","app.js"]&lt;/code&gt;) is preferred so SIGTERM reaches your process for graceful shutdown. That last detail signals real experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Cache invalidation cascades. If you &lt;code&gt;COPY . .&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; &lt;code&gt;RUN npm ci&lt;/code&gt;, then changing any source file busts the cache for the install layer and everything after — your dependencies reinstall on every build. Order from least-changing to most-changing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Use &lt;code&gt;npm ci&lt;/code&gt; instead of &lt;code&gt;npm install&lt;/code&gt; in Dockerfiles. It installs exactly what's in &lt;code&gt;package-lock.json&lt;/code&gt;, fails if the lockfile is out of sync, and is faster and fully reproducible — exactly what you want for deterministic image builds.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Multi-Stage Builds and Image Optimization
&lt;/h2&gt;

&lt;p&gt;A naive image bundles your entire build toolchain — compilers, dev dependencies, source code — into the thing you ship to production. That is bloated and insecure. Multi-stage builds let you build in one stage and copy &lt;em&gt;only the finished artifact&lt;/em&gt; into a clean, tiny final stage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why image size matters in interviews:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; fewer packages = smaller attack surface. No compiler, no shell utilities, no dev deps for an attacker to exploit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed:&lt;/strong&gt; smaller images pull and start faster — faster deploys, faster autoscaling, faster CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; less registry storage, less bandwidth, less disk on every node. At scale this is real money.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Shipping a single-stage image is like mailing someone a cake along with the entire kitchen — oven, mixer, flour bags and all. Multi-stage builds bake the cake in the kitchen, then ship &lt;em&gt;only the cake&lt;/em&gt; in a small box.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Multi-stage Dockerfile: React app served by nginx
&lt;/h3&gt;

&lt;p&gt;Build the static bundle with Node, then throw away Node entirely and serve the files with a tiny nginx image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ---- Stage 1: build ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build          &lt;span class="c"&gt;# produces /app/dist (or /app/build)&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 2: serve ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.27-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist /usr/share/nginx/html&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final image contains nginx and your static files only — no Node, no &lt;code&gt;node_modules&lt;/code&gt;, no source. Typically tens of MB instead of hundreds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-stage Dockerfile: Go binary on distroless
&lt;/h3&gt;

&lt;p&gt;Go compiles to a single static binary, so the final stage needs almost nothing — perfect for distroless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ---- Stage 1: build ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; /app/server .

&lt;span class="c"&gt;# ---- Stage 2: minimal runtime ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/static-debian12&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/server /server&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/server"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is often under 10 MB: just your binary plus minimal certs. No shell, no package manager — nothing for an attacker to pivot into.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimization techniques
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small base images:&lt;/strong&gt; prefer &lt;code&gt;-alpine&lt;/code&gt;, &lt;code&gt;-slim&lt;/code&gt;, or distroless over full &lt;code&gt;ubuntu&lt;/code&gt;/&lt;code&gt;debian&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-stage builds:&lt;/strong&gt; ship only the artifact, not the toolchain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine RUN layers:&lt;/strong&gt; chain with &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and clean up in the same layer so the cleanup actually shrinks the image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.dockerignore:&lt;/strong&gt; keep &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, logs out of the build context — faster builds and no accidental bloat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order instructions:&lt;/strong&gt; least-changing first for cache hits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove build deps:&lt;/strong&gt; uninstall compilers in the same &lt;code&gt;RUN&lt;/code&gt;, or just leave them in the discarded build stage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--no-install-recommends&lt;/code&gt;:&lt;/strong&gt; on Debian/Ubuntu, skip optional suggested packages.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Debian/Ubuntu: clean up in the SAME layer&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; curl ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical &lt;code&gt;.dockerignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.git
dist
build
*.log
.env
Dockerfile
.dockerignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Base image sizes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Base image&lt;/th&gt;
&lt;th&gt;Approx size&lt;/th&gt;
&lt;th&gt;Has shell / pkg manager?&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ubuntu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~75 MB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Dev convenience, lots of tooling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debian:slim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~30 MB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Balanced general-purpose runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alpine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~5 MB&lt;/td&gt;
&lt;td&gt;Yes (busybox + apk)&lt;/td&gt;
&lt;td&gt;Small images, most languages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;distroless&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~2–20 MB&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Most secure production runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Alpine is small but can cause issues with native modules due to musl libc. In those cases a &lt;code&gt;-slim&lt;/code&gt; variant or a proper multi-stage build is a better fit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Before / after: the real win
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker images
REPOSITORY      TAG          SIZE
myapp           single       1.1GB     &lt;span class="c"&gt;# node + source + node_modules&lt;/span&gt;
myapp           multistage   42MB      &lt;span class="c"&gt;# nginx + static build only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Distroless images contain your app and its runtime — and literally nothing else. No shell, no &lt;code&gt;apt&lt;/code&gt;, no &lt;code&gt;ls&lt;/code&gt;. That kills a whole class of attacks. The trade-off: you can't &lt;code&gt;docker exec&lt;/code&gt; in to poke around, so debug locally or use the &lt;code&gt;:debug&lt;/code&gt; variant. For compiled languages (Go, Rust) and even Java/Python, distroless is the gold-standard production base.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; "How would you reduce a Docker image's size?" Hit these in order: multi-stage build (biggest win), smaller base image (alpine/slim/distroless), &lt;code&gt;.dockerignore&lt;/code&gt;, combine and clean up RUN layers in the same instruction, and remove dev/build dependencies from the final stage. Bonus: mention the security angle — smaller image = smaller attack surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A team shipped a React app as a single-stage Node image at 1.1 GB. Pulls on every deploy took minutes and autoscaling lagged. Switching to a multi-stage build (Node to build, nginx-alpine to serve) dropped it to ~42 MB — a 25x reduction. Deploys went from minutes to seconds.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Data: Volumes vs Bind Mounts
&lt;/h2&gt;

&lt;p&gt;Containers are &lt;strong&gt;ephemeral&lt;/strong&gt;. The writable layer lives and dies with the container — &lt;code&gt;docker rm&lt;/code&gt; the container and that data is gone forever. That is fine for a stateless frontend, but a disaster for a database. To persist data, or to share files between your host and a container, you mount storage from &lt;em&gt;outside&lt;/em&gt; the container's lifecycle.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A container's own filesystem is like a hotel room — when you check out, housekeeping wipes everything. A &lt;strong&gt;volume&lt;/strong&gt; is your bank locker: it stays safe no matter how many times you check in and out. A &lt;strong&gt;bind mount&lt;/strong&gt; is like leaving a door open straight into your house — convenient, but the container can now mess with your real stuff.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Visual model:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Named volume&lt;/strong&gt; — container writes to &lt;code&gt;/var/lib/...&lt;/code&gt; → Docker-managed storage at &lt;code&gt;/var/lib/docker/volumes/pgdata&lt;/code&gt;. Portable, survives container removal, best for DB data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bind mount&lt;/strong&gt; — container writes to &lt;code&gt;/app&lt;/code&gt; → a specific host folder you pick (e.g. &lt;code&gt;/home/user/project&lt;/code&gt;). Best for live dev where you want host edits visible instantly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The three mount types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Named volume:&lt;/strong&gt; Docker-managed storage living under &lt;code&gt;/var/lib/docker/volumes/&lt;/code&gt;. Docker owns it; you reference it by name. Best for persistent app data like databases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bind mount:&lt;/strong&gt; maps a specific host path straight into the container. You control the exact location. Best for local development (live code).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tmpfs:&lt;/strong&gt; stored in host RAM only, never written to disk. Vanishes on stop. Best for secrets or scratch data you never want persisted.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Named volume&lt;/th&gt;
&lt;th&gt;Bind mount&lt;/th&gt;
&lt;th&gt;tmpfs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Location&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/var/lib/docker/volumes/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Any host path you pick&lt;/td&gt;
&lt;td&gt;Host RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed by&lt;/td&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Docker (memory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use case&lt;/td&gt;
&lt;td&gt;DB / persistent data&lt;/td&gt;
&lt;td&gt;Live dev, config files&lt;/td&gt;
&lt;td&gt;Secrets, scratch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portability&lt;/td&gt;
&lt;td&gt;High (Docker-native)&lt;/td&gt;
&lt;td&gt;Low (host-path dependent)&lt;/td&gt;
&lt;td&gt;N/A (ephemeral)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance on Mac/Win&lt;/td&gt;
&lt;td&gt;Fast (in VM)&lt;/td&gt;
&lt;td&gt;Slower (host↔VM sync)&lt;/td&gt;
&lt;td&gt;Fast (RAM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Survives container rm&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (it's on host)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;
Named volumes are Docker-managed and portable (best for production data); bind mounts expose a specific host directory (best for local development).





&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; On Mac/Windows, Docker runs inside a Linux VM, so bind mounts involve syncing between host and VM on every file change — hence the slowdown. Named volumes live entirely inside the VM and are therefore fast.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Managing named volumes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create pgdata          &lt;span class="c"&gt;# create&lt;/span&gt;
docker volume &lt;span class="nb"&gt;ls&lt;/span&gt;                     &lt;span class="c"&gt;# list&lt;/span&gt;
docker volume inspect pgdata         &lt;span class="c"&gt;# see mountpoint, driver&lt;/span&gt;
docker volume &lt;span class="nb"&gt;rm &lt;/span&gt;pgdata              &lt;span class="c"&gt;# delete&lt;/span&gt;
docker volume prune                  &lt;span class="c"&gt;# remove all unused volumes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Two mount syntaxes: -v vs --mount
&lt;/h3&gt;

&lt;p&gt;The short &lt;code&gt;-v&lt;/code&gt; form is compact; the verbose &lt;code&gt;--mount&lt;/code&gt; form is explicit and self-documenting (preferred in scripts).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# -v form: source:target[:options]&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; pgdata:/var/lib/postgresql/data postgres:16

&lt;span class="c"&gt;# --mount form: explicit key=value&lt;/span&gt;
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;volume,source&lt;span class="o"&gt;=&lt;/span&gt;pgdata,target&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/postgresql/data postgres:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bind mount for live development (hot reload)
&lt;/h3&gt;

&lt;p&gt;Mount your source code into the container so changes on your host instantly reflect inside — the dev server hot-reloads as you edit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5173:5173 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /app/node_modules &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; /app &lt;span class="se"&gt;\&lt;/span&gt;
  node:20-alpine npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second &lt;code&gt;-v /app/node_modules&lt;/code&gt; is an anonymous volume that &lt;em&gt;shields&lt;/em&gt; the container's installed &lt;code&gt;node_modules&lt;/code&gt; from being clobbered by the host bind mount — a very common gotcha.&lt;/p&gt;

&lt;h3&gt;
  
  
  Named volume for Postgres data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; pg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; pgdata:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
  postgres:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker rm -f pg&lt;/code&gt; destroys the container but &lt;code&gt;pgdata&lt;/code&gt; survives. Start a new Postgres container against the same volume and all your tables are still there.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to use which
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Named volume&lt;/strong&gt; → production data that must persist: databases, uploads, caches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bind mount&lt;/strong&gt; → local dev where you want host edits live in the container, or injecting a config file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tmpfs&lt;/strong&gt; → sensitive or throwaway data that should never touch disk.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; A bind mount &lt;em&gt;overlays&lt;/em&gt; the target directory — it hides whatever the image had there. If you bind-mount your source over &lt;code&gt;/app&lt;/code&gt; and the image baked &lt;code&gt;node_modules&lt;/code&gt; into &lt;code&gt;/app/node_modules&lt;/code&gt;, your mount hides them and the app breaks (hence the anonymous-volume trick above). Also watch file permissions: a process running as a non-root UID inside the container may not be able to write to a host directory owned by a different user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; "Where does a named volume's data actually live?" Answer: on the Docker host under &lt;code&gt;/var/lib/docker/volumes/&amp;lt;name&amp;gt;/_data&lt;/code&gt;, managed by Docker — not inside the container. On Mac/Windows it's inside the Docker Desktop Linux VM, which is why you can't &lt;code&gt;cd&lt;/code&gt; to it from your Mac terminal. Knowing this distinction (host vs container vs VM) shows you actually understand the storage model.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Networking
&lt;/h2&gt;

&lt;p&gt;Every container needs to talk to something — the host, the internet, or another container. Docker handles this through &lt;strong&gt;network drivers&lt;/strong&gt;. When you install Docker on Ubuntu 24.04, three networks already exist out of the box: &lt;code&gt;bridge&lt;/code&gt;, &lt;code&gt;host&lt;/code&gt;, and &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Think of a Docker network like the wiring in an apartment building. &lt;code&gt;bridge&lt;/code&gt; is the building's internal intercom — flats can call each other through a switchboard, and the building has one main door (the host) to the outside world. &lt;code&gt;host&lt;/code&gt; means the flat has no walls at all and shares the building's street address directly. &lt;code&gt;none&lt;/code&gt; is a sealed room — no phone, no door, total isolation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Container-to-container on a user-defined bridge:&lt;/strong&gt; On a user-defined network, Docker's embedded DNS lets containers reach each other by name. Only the API container needs a published port (&lt;code&gt;-p 3000:3000&lt;/code&gt;) so the browser can reach it. The &lt;code&gt;db&lt;/code&gt; and &lt;code&gt;cache&lt;/code&gt; containers are never published to the host — secure by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  The three default networks
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Driver&lt;/th&gt;
&lt;th&gt;Isolation&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Performance&lt;/th&gt;
&lt;th&gt;Port Mapping&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;bridge&lt;/strong&gt; (default)&lt;/td&gt;
&lt;td&gt;Containers get private IPs on a virtual subnet; isolated from host network namespace.&lt;/td&gt;
&lt;td&gt;The normal default for single-host apps.&lt;/td&gt;
&lt;td&gt;Slight overhead from NAT + virtual bridge, negligible for most workloads.&lt;/td&gt;
&lt;td&gt;Required. You must &lt;code&gt;-p&lt;/code&gt; to reach a container from outside.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;host&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None. Container shares the host's network namespace directly.&lt;/td&gt;
&lt;td&gt;Max network performance, or when you need the container on the host's real IP.&lt;/td&gt;
&lt;td&gt;Fastest — no NAT, no bridge, no virtual layer.&lt;/td&gt;
&lt;td&gt;Ignored. &lt;code&gt;-p&lt;/code&gt; does nothing; the container binds host ports directly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Total. Only a loopback interface; no external connectivity.&lt;/td&gt;
&lt;td&gt;Security-sensitive batch jobs, or you wire up custom networking yourself.&lt;/td&gt;
&lt;td&gt;N/A — no traffic flows.&lt;/td&gt;
&lt;td&gt;N/A — nothing to publish.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Port mapping deep dive
&lt;/h3&gt;

&lt;p&gt;A container on a bridge network lives on a private subnet (commonly &lt;code&gt;172.17.0.0/16&lt;/code&gt;). The flag &lt;code&gt;-p hostPort:containerPort&lt;/code&gt; tells Docker to take traffic arriving on that host port and forward it (via iptables DNAT rules) into the container's port. Read it as &lt;strong&gt;outside:inside&lt;/strong&gt; — the left number is what you type in the browser; the right number is what the app listens on inside the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Map host port 8080 -&amp;gt; container port 80&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; web nginx

&lt;span class="c"&gt;# Now the host can reach the container:&lt;/span&gt;
curl http://localhost:8080

&lt;span class="c"&gt;# Map only on loopback (NOT exposed to the LAN):&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; web-local nginx

&lt;span class="c"&gt;# Let Docker pick a random free host port:&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80 &lt;span class="nt"&gt;--name&lt;/span&gt; web-rand nginx
docker port web-rand   &lt;span class="c"&gt;# shows which host port was assigned&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Container-to-container communication — the key part
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Interview scenario:&lt;/strong&gt; "Why can't my two containers talk by name?"&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;default&lt;/strong&gt; bridge network, containers can reach each other by IP address, but there is &lt;strong&gt;no automatic DNS&lt;/strong&gt; — you cannot ping or curl another container by its name. On a &lt;strong&gt;user-defined&lt;/strong&gt; bridge network, Docker runs an embedded DNS server and containers resolve each other &lt;strong&gt;by container name automatically&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the root cause of 90% of "my app can't connect to my db" bugs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Almost never use the default bridge for multi-container apps. Always &lt;code&gt;docker network create&lt;/code&gt; a user-defined bridge (or let Docker Compose make one for you — it does this automatically). You get free DNS by container name, better isolation, and you can attach/detach containers on the fly.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Create a user-defined bridge network&lt;/span&gt;
docker network create appnet

&lt;span class="c"&gt;# List networks (you'll see appnet alongside bridge/host/none)&lt;/span&gt;
docker network &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# Inspect it: subnet, gateway, connected containers&lt;/span&gt;
docker network inspect appnet

&lt;span class="c"&gt;# 2. Run a db container ON that network&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; db &lt;span class="nt"&gt;--network&lt;/span&gt; appnet &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret postgres:16

&lt;span class="c"&gt;# 3. Run a client container on the SAME network&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; client &lt;span class="nt"&gt;--network&lt;/span&gt; appnet alpine sh

&lt;span class="c"&gt;# Inside the client shell — resolve 'db' by NAME, no IP needed:&lt;/span&gt;
&lt;span class="c"&gt;#   apk add --no-cache curl&lt;/span&gt;
&lt;span class="c"&gt;#   ping db&lt;/span&gt;
&lt;span class="c"&gt;#   nc -zv db 5432          # TCP connect to postgres by name&lt;/span&gt;

&lt;span class="c"&gt;# Attach an already-running container to a network later:&lt;/span&gt;
docker network connect appnet web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; "bridge vs host?" — bridge isolates the container on its own subnet and needs &lt;code&gt;-p&lt;/code&gt; port publishing + NAT to be reached; host removes that isolation, the container uses the host's network stack directly (no &lt;code&gt;-p&lt;/code&gt;, fastest, but port conflicts and less isolation). "Why can't my two containers talk by name on the default bridge?" — because the default bridge has no embedded DNS; name resolution only works on user-defined networks. Fix: create a user-defined bridge and run both containers on it (or use Compose).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; When you write &lt;code&gt;-p 8080:80&lt;/code&gt;, Docker binds &lt;code&gt;0.0.0.0:8080&lt;/code&gt; by default — meaning the port is exposed on &lt;em&gt;every&lt;/em&gt; network interface, including your public/LAN IP, not just localhost. On a cloud VM with a public IP, that database or admin panel is now reachable from the internet. Bind to &lt;code&gt;127.0.0.1&lt;/code&gt; (e.g. &lt;code&gt;-p 127.0.0.1:8080:80&lt;/code&gt;) for local-only access. Docker's iptables rules can also bypass a UFW firewall — don't assume UFW alone protects published ports.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Compose
&lt;/h2&gt;

&lt;p&gt;Running one container with a long &lt;code&gt;docker run&lt;/code&gt; command is fine. But a real app is a &lt;em&gt;web frontend + an API + a database + a cache&lt;/em&gt;, each needing its own ports, volumes, env vars, and a shared network. Typing four &lt;code&gt;docker run&lt;/code&gt; commands in the right order, every time, is painful and error-prone. &lt;strong&gt;Docker Compose&lt;/strong&gt; solves this: you declare your entire multi-container stack in one YAML file and bring it all up with a single command. Use this with the &lt;a href="https://opscanopy.com/docker-run-to-compose/" rel="noopener noreferrer"&gt;Docker run → Compose converter&lt;/a&gt; to translate existing &lt;code&gt;docker run&lt;/code&gt; commands into Compose services quickly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If &lt;code&gt;docker run&lt;/code&gt; is cooking one dish, Compose is the full recipe card for a thali — it lists every dish (service), the ingredients (image, env, volumes), and the order to serve them (depends_on). One command and the whole meal arrives, plated the same way every single time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Compose stack layout:&lt;/strong&gt; A single &lt;code&gt;docker compose up&lt;/code&gt; creates the network, the volume, and all three services. The &lt;code&gt;api&lt;/code&gt; service builds from a local Dockerfile; &lt;code&gt;db&lt;/code&gt; uses a named volume for persistence; all three are wired by service name via Compose's built-in DNS.&lt;/p&gt;

&lt;h3&gt;
  
  
  "docker compose" vs "docker-compose"
&lt;/h3&gt;

&lt;p&gt;Old Compose was a separate Python tool invoked as &lt;code&gt;docker-compose&lt;/code&gt; (with a hyphen). Modern Compose is a &lt;strong&gt;v2 plugin&lt;/strong&gt; built into the Docker CLI, invoked as &lt;code&gt;docker compose&lt;/code&gt; (a space, no hyphen). On Ubuntu 24.04 with current Docker, use &lt;strong&gt;&lt;code&gt;docker compose&lt;/code&gt;&lt;/strong&gt;. The hyphenated command is legacy and deprecated. Also note: the top-level &lt;code&gt;version:&lt;/code&gt; key is now obsolete and can be omitted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomy of a Compose file
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull a prebuilt image (e.g. &lt;code&gt;postgres:16&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build from a local Dockerfile instead of pulling.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ports&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publish ports to the host — &lt;code&gt;"host:container"&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;volumes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mount named volumes or host paths for persistent data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set env vars inside the container.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;depends_on&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Control start order (and optionally wait for health).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;networks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attach the service to one or more networks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;healthcheck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Define how Docker tests if the service is actually ready.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  A full, realistic compose.yaml
&lt;/h3&gt;

&lt;p&gt;A Node API built from a local Dockerfile, a Postgres database with a named volume, and a Redis cache — all on a shared network. The API waits for Postgres to be &lt;em&gt;healthy&lt;/em&gt;, not just started.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./api&lt;/span&gt;                 &lt;span class="c1"&gt;# builds from ./api/Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;                &lt;span class="c1"&gt;# resolves by service name (Compose DNS)&lt;/span&gt;
      &lt;span class="na"&gt;DB_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
      &lt;span class="na"&gt;DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appuser&lt;/span&gt;
      &lt;span class="na"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_PASSWORD}&lt;/span&gt;    &lt;span class="c1"&gt;# pulled from .env file&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://cache:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;   &lt;span class="c1"&gt;# wait until db healthcheck passes&lt;/span&gt;
      &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_started&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;appnet&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appuser&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_PASSWORD}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appdb&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;   &lt;span class="c1"&gt;# named volume = data survives restarts&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;appuser&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;appdb"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;appnet&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;appnet&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pgdata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                        &lt;span class="c1"&gt;# Docker-managed named volume&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;appnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                        &lt;span class="c1"&gt;# user-defined bridge -&amp;gt; DNS by service name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because every service is on &lt;code&gt;appnet&lt;/code&gt;, the API reaches the database at hostname &lt;code&gt;db&lt;/code&gt; and Redis at &lt;code&gt;cache&lt;/code&gt; — the service names &lt;em&gt;are&lt;/em&gt; the hostnames, thanks to Compose's user-defined network (exactly the DNS behaviour covered in the networking section).&lt;/p&gt;

&lt;h3&gt;
  
  
  Key commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;          &lt;span class="c"&gt;# build (if needed) + start all, detached&lt;/span&gt;
docker compose ps             &lt;span class="c"&gt;# list services in this project + their state&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;        &lt;span class="c"&gt;# tail combined logs from all services&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; api    &lt;span class="c"&gt;# tail just one service&lt;/span&gt;
docker compose build          &lt;span class="c"&gt;# (re)build images that use 'build:'&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;api sh    &lt;span class="c"&gt;# open a shell inside the running api container&lt;/span&gt;
docker compose down           &lt;span class="c"&gt;# stop + remove containers and the network&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt;        &lt;span class="c"&gt;# ALSO delete named volumes (wipes db data!)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  depends_on vs healthcheck — the trap
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Interview scenario:&lt;/strong&gt; "Does &lt;code&gt;depends_on&lt;/code&gt; wait for a service to be ready?"&lt;/p&gt;

&lt;p&gt;A plain &lt;code&gt;depends_on: [db]&lt;/code&gt; only controls &lt;strong&gt;start order&lt;/strong&gt;. Compose will start &lt;code&gt;db&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; &lt;code&gt;api&lt;/code&gt; — but "started" just means the container process launched, &lt;strong&gt;not&lt;/strong&gt; that Postgres is accepting connections. Databases take a few seconds to initialise, so your API can crash on boot trying to connect to a not-yet-ready DB.&lt;/p&gt;

&lt;p&gt;For readiness, add a &lt;code&gt;healthcheck&lt;/code&gt; to the dependency service and use &lt;code&gt;condition: service_healthy&lt;/code&gt; in the dependent service — only then does Compose wait.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Keep secrets and environment-specific values out of the compose file. Put them in a &lt;code&gt;.env&lt;/code&gt; file (auto-loaded by Compose for &lt;code&gt;${VAR}&lt;/code&gt; substitution) and add &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;. For per-environment tweaks, use a &lt;code&gt;compose.override.yaml&lt;/code&gt; (auto-merged in dev) or &lt;code&gt;docker compose -f compose.yaml -f compose.prod.yaml up&lt;/code&gt; to layer a production config on top. Use the &lt;a href="https://opscanopy.com/env-example-checker/" rel="noopener noreferrer"&gt;.env Example Checker&lt;/a&gt; to validate that your &lt;code&gt;.env.example&lt;/code&gt; stays in sync with your actual &lt;code&gt;.env&lt;/code&gt; keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A new dev joining a team, instead of working through a half-day setup doc, can clone the repo, copy &lt;code&gt;.env.example&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt;, and run &lt;code&gt;docker compose up -d&lt;/code&gt;. Sixty seconds later the API, database, and cache are running and wired together identically to every other machine — including CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Expect: "What does Compose actually do for you?" — declarative multi-container orchestration on a &lt;em&gt;single host&lt;/em&gt;: networks, volumes, env, and start order in one file. Be ready for "Does depends_on wait for the service to be ready?" — No, only start order; for readiness add a healthcheck + &lt;code&gt;condition: service_healthy&lt;/code&gt;. Note the scope: Compose is single-host; for multi-host/cluster orchestration you move to Kubernetes — see &lt;a href="https://opscanopy.com/learn/guides/kubernetes-for-devops/" rel="noopener noreferrer"&gt;Kubernetes for DevOps&lt;/a&gt; for a full introduction. See also the &lt;a href="https://opscanopy.com/kubernetes-resource-calculator/" rel="noopener noreferrer"&gt;Kubernetes Resource Calculator&lt;/a&gt; when you are ready to plan resource requests for a containerised workload running in a cluster. Mentioning this boundary shows you understand where Compose stops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;code&gt;docker compose down -v&lt;/code&gt; deletes named volumes — that &lt;code&gt;pgdata&lt;/code&gt; volume holding your database goes with it. Great for a clean dev reset, catastrophic on anything you care about. Plain &lt;code&gt;docker compose down&lt;/code&gt; (no &lt;code&gt;-v&lt;/code&gt;) keeps volumes. Build the muscle memory now, before you run it on the wrong project.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Best Practices and Security
&lt;/h2&gt;

&lt;p&gt;A container that &lt;em&gt;works&lt;/em&gt; and a container that is &lt;em&gt;production-ready&lt;/em&gt; are different things. This section is the checklist that separates "it runs on my machine" from "it's safe to ship." Most of it shows up in DevOps interviews too — security questions are nearly guaranteed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run as non-root
&lt;/h3&gt;

&lt;p&gt;By default, processes in a container run as &lt;strong&gt;root&lt;/strong&gt; (UID 0). If an attacker breaks out of the app, they are root inside the container — and a container root maps to host root in several breakout scenarios. The fix is the &lt;code&gt;USER&lt;/code&gt; instruction: create an unprivileged user and switch to it before the app runs. This is the single highest-impact security change you can make.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-slim&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Install deps as root (needs write to /app)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Create a non-root user and hand over ownership&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 10001 &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; appuser &lt;span class="se"&gt;\
&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appuser /app

&lt;span class="c"&gt;# Everything from here runs as appuser, not root&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  .dockerignore
&lt;/h3&gt;

&lt;p&gt;Just like &lt;code&gt;.gitignore&lt;/code&gt;, a &lt;code&gt;.dockerignore&lt;/code&gt; file tells the build to skip files when sending the build context to the daemon. This makes builds faster, images smaller, and — critically — stops secrets and junk from being copied in by a broad &lt;code&gt;COPY . .&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.*
*.md
Dockerfile
.dockerignore
dist
coverage
.vscode
.DS_Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Excluding &lt;code&gt;node_modules&lt;/code&gt; forces a clean &lt;code&gt;npm ci&lt;/code&gt; inside the image (no host-OS native modules leaking in), and excluding &lt;code&gt;.env&lt;/code&gt; / &lt;code&gt;.git&lt;/code&gt; prevents secrets and history from ever entering the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't bake secrets into images
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Interview scenario:&lt;/strong&gt; "How do you handle secrets in Docker?"&lt;/p&gt;

&lt;p&gt;Anything you put in an &lt;code&gt;ENV&lt;/code&gt; instruction or pass as a build &lt;code&gt;ARG&lt;/code&gt; is baked into an image layer — and image layers are &lt;strong&gt;readable by anyone with the image&lt;/strong&gt;. &lt;code&gt;docker history&lt;/code&gt; dumps them right out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# An attacker (or teammate) with your image can read baked-in values:&lt;/span&gt;
docker &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--no-trunc&lt;/span&gt; your-image:tag

&lt;span class="c"&gt;# WRONG — secret is now permanently in a layer:&lt;/span&gt;
&lt;span class="c"&gt;#   ENV API_KEY=sk_live_abcd1234&lt;/span&gt;
&lt;span class="c"&gt;#   ARG DB_PASSWORD   (visible in history even if "removed" later)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, inject secrets at &lt;strong&gt;runtime&lt;/strong&gt;, never at build time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Runtime env (not in the image, but visible via 'docker inspect'):&lt;/span&gt;
docker run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; myapp

&lt;span class="c"&gt;# Better: file-based secrets (Compose/Swarm) mounted at /run/secrets/...&lt;/span&gt;
&lt;span class="c"&gt;# Best: an external secrets manager (Vault, AWS Secrets Manager,&lt;/span&gt;
&lt;span class="c"&gt;#       SSM Parameter Store) fetched by the app at startup.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Image scanning
&lt;/h3&gt;

&lt;p&gt;Your base image and dependencies carry known vulnerabilities (CVEs). Scan before you ship, and re-scan regularly — new CVEs are disclosed against old images all the time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Docker Scout (built into modern Docker Desktop / CLI)&lt;/span&gt;
docker scout cves your-image:tag
docker scout quickview your-image:tag

&lt;span class="c"&gt;# Trivy (popular open-source scanner) on Ubuntu 24.04&lt;/span&gt;
trivy image your-image:tag
trivy image &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH,CRITICAL your-image:tag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hardening checklist
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Do&lt;/th&gt;
&lt;th&gt;Don't&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pin base images by version &lt;em&gt;and&lt;/em&gt; digest: &lt;code&gt;node:20-slim@sha256:…&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;:latest&lt;/code&gt; — it silently changes under you.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use minimal images (&lt;code&gt;-slim&lt;/code&gt;, &lt;code&gt;alpine&lt;/code&gt;, &lt;code&gt;distroless&lt;/code&gt;) — smaller attack surface.&lt;/td&gt;
&lt;td&gt;Ship a full &lt;code&gt;ubuntu&lt;/code&gt; base with compilers and shells you don't need.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run as a non-root &lt;code&gt;USER&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Run the app as root.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drop Linux capabilities: &lt;code&gt;--cap-drop ALL&lt;/code&gt;, add back only what's needed.&lt;/td&gt;
&lt;td&gt;Run with default (or &lt;code&gt;--privileged&lt;/code&gt;) capabilities.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run read-only where possible: &lt;code&gt;--read-only&lt;/code&gt; + &lt;code&gt;--tmpfs /tmp&lt;/code&gt;.&lt;/td&gt;
&lt;td&gt;Leave the whole filesystem writable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add a &lt;code&gt;HEALTHCHECK&lt;/code&gt; so orchestrators know real readiness.&lt;/td&gt;
&lt;td&gt;Assume "process running" means "service healthy."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inject secrets at runtime via env files / secrets managers.&lt;/td&gt;
&lt;td&gt;Bake secrets into &lt;code&gt;ENV&lt;/code&gt;/&lt;code&gt;ARG&lt;/code&gt; layers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use multi-stage builds to keep build tools out of the final image.&lt;/td&gt;
&lt;td&gt;Ship the entire build toolchain in production.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Least-privilege runtime: drop all caps, read-only root fs, writable /tmp&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-drop&lt;/span&gt; ALL &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--read-only&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; myapp myapp:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --start-period=10s --retries=3 \&lt;/span&gt;
  CMD curl -f http://localhost:3000/health || exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Secrets in image layers are &lt;strong&gt;permanent and readable by anyone with the image&lt;/strong&gt;. Deleting the &lt;code&gt;ENV&lt;/code&gt; line in a later layer does NOT remove it — the earlier layer still contains it, and &lt;code&gt;docker history --no-trunc&lt;/code&gt; reveals it. If you ever accidentally bake a real credential into an image and push it, treat that credential as &lt;em&gt;compromised&lt;/em&gt;: rotate it immediately. You can't "edit it out" of a published image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Container security questions cluster around a few themes: (1) run as non-root (&lt;code&gt;USER&lt;/code&gt;); (2) minimal/pinned base images to shrink attack surface; (3) never bake secrets into layers (explain &lt;code&gt;docker history&lt;/code&gt;); (4) scan images for CVEs (Scout/Trivy) in CI; (5) least privilege at runtime — drop capabilities, read-only fs, no &lt;code&gt;--privileged&lt;/code&gt;. Bonus: the container shares the host kernel, so a kernel exploit can escape — keep the host patched and consider extra isolation (gVisor, rootless Docker).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Bake scanning into CI as a gate: fail the pipeline on &lt;code&gt;HIGH&lt;/code&gt;/&lt;code&gt;CRITICAL&lt;/code&gt; CVEs (&lt;code&gt;trivy image --exit-code 1 --severity HIGH,CRITICAL&lt;/code&gt;). Combined with pinned base digests, this means an image can't silently drift into a vulnerable state between builds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A fintech team shipped a Node image with &lt;code&gt;ENV STRIPE_KEY=sk_live_…&lt;/code&gt; baked in "temporarily." The image was pushed to a registry a contractor could pull. Months later, during an audit, &lt;code&gt;docker history&lt;/code&gt; surfaced the live key sitting in a layer. They had to rotate the Stripe key, invalidate the image across every environment, and add a CI secret-scanner — a week of cleanup that one runtime &lt;code&gt;-e&lt;/code&gt; flag (or a secrets manager) would have prevented entirely.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Troubleshooting Playbook
&lt;/h2&gt;

&lt;p&gt;Think of this like a diagnostic chart: a container presents a &lt;strong&gt;symptom&lt;/strong&gt;, you run a &lt;strong&gt;diagnose command&lt;/strong&gt; to confirm the &lt;strong&gt;cause&lt;/strong&gt;, then you apply the &lt;strong&gt;fix&lt;/strong&gt;. You see a blank screen (symptom), open DevTools console (diagnose), spot the error (cause), patch the code (fix). Containers are no different — the tools just have different names.&lt;/p&gt;

&lt;p&gt;90% of Docker problems are solved by three commands run in order: see what state the container is in (&lt;code&gt;docker ps -a&lt;/code&gt;), read what it logged before dying (&lt;code&gt;docker logs&lt;/code&gt;), then either inspect its config or jump inside it (&lt;code&gt;docker inspect&lt;/code&gt; / &lt;code&gt;docker exec -it&lt;/code&gt;). Master this loop and you will look like a wizard in interviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symptom → Diagnose → Fix chart
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Diagnose (command)&lt;/th&gt;
&lt;th&gt;Likely Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Container won't start / stuck in &lt;code&gt;Created&lt;/code&gt; or &lt;code&gt;Restarting&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker ps -a&lt;/code&gt; then &lt;code&gt;docker logs &amp;lt;name&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Bad entrypoint, missing config/env, app crashes on boot, or a restart loop&lt;/td&gt;
&lt;td&gt;Read logs, fix the underlying error, rebuild and re-run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container exits immediately (&lt;code&gt;Exited (0)&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker ps -a&lt;/code&gt; + &lt;code&gt;docker logs &amp;lt;name&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No long-running foreground process; &lt;code&gt;CMD&lt;/code&gt; finished and nothing kept PID 1 alive&lt;/td&gt;
&lt;td&gt;Run a process that stays in the foreground (e.g. &lt;code&gt;node server.js&lt;/code&gt;); don't background your main process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exits with non-zero code (&lt;code&gt;Exited (1)&lt;/code&gt;, &lt;code&gt;Exited (127)&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker logs &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;App threw on startup; &lt;code&gt;127&lt;/code&gt; usually means "command not found"&lt;/td&gt;
&lt;td&gt;Fix the app error; for &lt;code&gt;127&lt;/code&gt;, verify the binary exists and the &lt;code&gt;CMD&lt;/code&gt; path is correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image is huge (1GB+ for a small app)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker images&lt;/code&gt; and &lt;code&gt;docker history &amp;lt;image&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Full base image, build tools shipped, no &lt;code&gt;.dockerignore&lt;/code&gt;, dev deps baked in&lt;/td&gt;
&lt;td&gt;Multi-stage build, slim/alpine base, add &lt;code&gt;.dockerignore&lt;/code&gt;, combine &lt;code&gt;RUN&lt;/code&gt; layers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;port is already allocated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker ps&lt;/code&gt; then &lt;code&gt;sudo lsof -i :3000&lt;/code&gt; (or `ss -ltnp \&lt;/td&gt;
&lt;td&gt;grep 3000`)&lt;/td&gt;
&lt;td&gt;Another container or host process is bound to that port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;permission denied&lt;/code&gt; on a bind-mounted folder&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker exec -it &amp;lt;name&amp;gt; ls -ln /path&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; inside&lt;/td&gt;
&lt;td&gt;UID/GID inside container ≠ host file owner; or SELinux/AppArmor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--user $(id -u):$(id -g)&lt;/code&gt;, &lt;code&gt;chown&lt;/code&gt; the host dir, or add &lt;code&gt;:z&lt;/code&gt;/&lt;code&gt;:Z&lt;/code&gt; on SELinux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container A can't reach B by name&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker network inspect bridge&lt;/code&gt;; &lt;code&gt;docker exec -it A ping B&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Both on the &lt;strong&gt;default&lt;/strong&gt; &lt;code&gt;bridge&lt;/code&gt;, which has no DNS by name&lt;/td&gt;
&lt;td&gt;Create a user-defined bridge and attach both: &lt;code&gt;docker network create appnet&lt;/code&gt; + &lt;code&gt;--network appnet&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no space left on device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker system df&lt;/code&gt; and &lt;code&gt;df -h&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Dangling images, stopped containers, unused volumes, build cache eating disk&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker system prune -af&lt;/code&gt; (add &lt;code&gt;--volumes&lt;/code&gt; only if sure)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cannot pull (&lt;code&gt;denied&lt;/code&gt;, &lt;code&gt;unauthorized&lt;/code&gt;, &lt;code&gt;toomanyrequests&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Read the exact error from &lt;code&gt;docker pull &amp;lt;image&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Not logged in, wrong tag, or Docker Hub anonymous rate limit&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker login&lt;/code&gt;; fix the tag; for rate limits, authenticate or wait&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code changes not reflecting after rebuild&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker build&lt;/code&gt; output (watch for &lt;code&gt;CACHED&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Cached layer reused; or running an old image/container&lt;/td&gt;
&lt;td&gt;Rebuild with &lt;code&gt;--no-cache&lt;/code&gt;, re-tag and re-run; in dev, bind-mount source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Env var not set inside the container&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker exec -it &amp;lt;name&amp;gt; env&lt;/code&gt; or &lt;code&gt;docker inspect&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-e&lt;/code&gt;/&lt;code&gt;--env-file&lt;/code&gt; not passed, or it's build-time &lt;code&gt;ARG&lt;/code&gt; not runtime &lt;code&gt;ENV&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pass &lt;code&gt;-e KEY=value&lt;/code&gt; or &lt;code&gt;--env-file .env&lt;/code&gt;; don't confuse &lt;code&gt;ARG&lt;/code&gt; with &lt;code&gt;ENV&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The general debugging workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. What is the state? (running, exited, restarting — and the exit code)&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;# 2. What did it say before it died? (your single most useful command)&lt;/span&gt;
docker logs &amp;lt;container_name_or_id&amp;gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; &amp;lt;container&amp;gt;          &lt;span class="c"&gt;# follow live, like tail -f&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 50 &amp;lt;container&amp;gt;   &lt;span class="c"&gt;# last 50 lines only&lt;/span&gt;

&lt;span class="c"&gt;# 3. How is it configured? (env, mounts, network, IP, command)&lt;/span&gt;
docker inspect &amp;lt;container&amp;gt;
docker inspect &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.ExitCode}}'&lt;/span&gt; &amp;lt;container&amp;gt;   &lt;span class="c"&gt;# just the exit code&lt;/span&gt;

&lt;span class="c"&gt;# 4. Get a shell inside a RUNNING container and poke around&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;container&amp;gt; sh     &lt;span class="c"&gt;# or 'bash' if the image has it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
Four-step crash-debug loop: check state, read logs, reproduce interactively, then fix and rebuild.





&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; A classic question is "a container starts and immediately stops, how do you debug it?" The winning answer names the loop: &lt;code&gt;docker ps -a&lt;/code&gt; to confirm it exited and see the code, &lt;code&gt;docker logs&lt;/code&gt; to read the error, then explain the most common cause — there was no long-running foreground process keeping PID 1 alive, or the app crashed on startup. If you can't &lt;code&gt;exec&lt;/code&gt; into it (because it already exited), mention you can override the entrypoint to debug: &lt;code&gt;docker run -it --entrypoint sh &amp;lt;image&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Deep dive: container exits immediately
&lt;/h3&gt;

&lt;p&gt;This is the #1 beginner trap and the #1 interview question. A container lives only as long as its main process (PID 1) runs. The moment that process exits, the container exits. It is like running &lt;code&gt;npm run build&lt;/code&gt; and expecting a dev server to stay up — build finishes, process ends, done. You needed &lt;code&gt;npm start&lt;/code&gt; (a long-running server) instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Confirm it exited and check the code&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="c"&gt;# "Exited (0)" = process finished cleanly, nothing to keep it alive&lt;/span&gt;
&lt;span class="c"&gt;# "Exited (1)" or higher = app crashed; read the logs&lt;/span&gt;

docker logs &amp;lt;container&amp;gt;

&lt;span class="c"&gt;# Can't exec because it already died? Override the entrypoint to get a shell:&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; sh myimage
&lt;span class="c"&gt;# Now run your CMD by hand and watch it fail in real time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# WRONG — the &amp;amp; backgrounds it, CMD returns instantly, container dies&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; node server.js &amp;amp;&lt;/span&gt;

&lt;span class="c"&gt;# RIGHT — runs in the foreground, holds the container open&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deep dive: port is already allocated
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find who is holding host port 3000&lt;/span&gt;
docker ps                         &lt;span class="c"&gt;# is another container mapping :3000?&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;lsof &lt;span class="nt"&gt;-i&lt;/span&gt; :3000                &lt;span class="c"&gt;# any host process on it?&lt;/span&gt;
ss &lt;span class="nt"&gt;-ltnp&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; :3000             &lt;span class="c"&gt;# alternative on Ubuntu 24.04&lt;/span&gt;

&lt;span class="c"&gt;# Fix A: stop the offender&lt;/span&gt;
docker stop &amp;lt;other_container&amp;gt;

&lt;span class="c"&gt;# Fix B: just map to a free host port (left = host, right = container)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3001:3000 myimage
&lt;span class="c"&gt;# App still listens on 3000 inside; you reach it at http://localhost:3001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deep dive: image too big
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See sizes and which layers are fat&lt;/span&gt;
docker images
docker &lt;span class="nb"&gt;history&lt;/span&gt; &amp;lt;image&amp;gt;     &lt;span class="c"&gt;# shows size added per layer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Multi-stage: build stage has all the tooling, final stage is slim&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deep dive: no space left on device
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See the breakdown: images, containers, volumes, build cache&lt;/span&gt;
docker system &lt;span class="nb"&gt;df
&lt;/span&gt;docker system &lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;        &lt;span class="c"&gt;# verbose, per-item&lt;/span&gt;

&lt;span class="c"&gt;# Reclaim everything unused (stopped containers, dangling/unused images, networks, build cache)&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-af&lt;/span&gt;    &lt;span class="c"&gt;# -a = also unused images, -f = no prompt&lt;/span&gt;

&lt;span class="c"&gt;# Target just one type if you prefer&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-af&lt;/span&gt;
docker container prune &lt;span class="nt"&gt;-f&lt;/span&gt;
docker builder prune &lt;span class="nt"&gt;-af&lt;/span&gt;   &lt;span class="c"&gt;# build cache only&lt;/span&gt;

&lt;span class="c"&gt;# Include volumes too — DANGER, can delete data (see warning)&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="nt"&gt;--volumes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;code&gt;docker system prune --volumes&lt;/code&gt; and &lt;code&gt;docker volume prune&lt;/code&gt; delete &lt;strong&gt;anonymous and unused volumes&lt;/strong&gt; — and that means real, persistent data (databases, uploads) can be wiped permanently. There is no undo. Never run &lt;code&gt;prune --volumes&lt;/code&gt; on a production host without first confirming which volumes are in use (&lt;code&gt;docker volume ls&lt;/code&gt;). When in doubt, prune images and build cache only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Run &lt;code&gt;docker system df&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; you blindly prune — it tells you whether the space is in images, volumes, or build cache, so you can reclaim surgically. Also, &lt;code&gt;docker logs&lt;/code&gt; only works if the app writes to &lt;code&gt;stdout&lt;/code&gt;/&lt;code&gt;stderr&lt;/code&gt;; apps that log to a file inside the container will show nothing, so always configure your app to log to the console in containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Prefer a day-by-day path? This is covered in &lt;a href="https://opscanopy.com/mission-90/" rel="noopener noreferrer"&gt;&lt;strong&gt;Mission 90 Days 22–30&lt;/strong&gt;&lt;/a&gt; — a free 90-day guided DevOps program with browser terminal missions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A team's CI runner kept failing builds with &lt;code&gt;no space left on device&lt;/code&gt; every Friday. &lt;code&gt;docker system df&lt;/code&gt; revealed 40 GB of build cache. They added &lt;code&gt;docker builder prune -af --filter "until=168h"&lt;/code&gt; (clear cache older than 7 days) to a nightly cron job. Builds stopped failing, and they never touched volumes, so no data was at risk. Diagnose with &lt;code&gt;system df&lt;/code&gt; first, then prune the specific thing that grew.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>docker</category>
      <category>interview</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Docker for DevOps: A Deep Dive into Images, Layers, and Compose</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Sun, 16 Aug 2026 15:30:40 +0000</pubDate>
      <link>https://dev.to/opscanopy/docker-for-devops-a-deep-dive-into-images-layers-and-compose-1a6c</link>
      <guid>https://dev.to/opscanopy/docker-for-devops-a-deep-dive-into-images-layers-and-compose-1a6c</guid>
      <description>&lt;p&gt;Docker is the tool that finally made "it works on my machine" a non-excuse. By packaging an application together with its exact dependencies into a portable image, Docker lets the same artifact run identically on a laptop, a CI runner, and a production server. This guide is a deep, practical tour for DevOps engineers: how images and layers actually work, how to write efficient and secure Dockerfiles, how containers talk to each other over networks, how to persist data with volumes, how to compose multi-service stacks, and how to take all of it to production. Work through it top to bottom, or jump to the part you need. For a structured learning path beyond this guide, see the &lt;a href="https://opscanopy.com/learn/roadmaps/docker/" rel="noopener noreferrer"&gt;Docker roadmap&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Docker Solves
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The "Works on My Machine" Story
&lt;/h3&gt;

&lt;p&gt;It's 2019. A developer at a startup has been grinding for &lt;strong&gt;3 weeks&lt;/strong&gt; on a Node.js app — a real-time dashboard for tracking delivery agents. The UI is slick, the APIs are fast, the PM is happy. He types &lt;code&gt;npm start&lt;/code&gt; and everything just works. Perfectly.&lt;/p&gt;

&lt;p&gt;He pushes the code to staging. The CI pipeline goes green. He Slacks his team: &lt;em&gt;"Deployed! Please test."&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;3 minutes later:&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Cannot find module 'optional-chaining-polyfill'
Error: node: /lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.28' not found
npm ERR! peer dep missing: react@^17.0.0, required by react-query@3.39.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;His laptop: &lt;strong&gt;Node 14.18, npm 6.14, Ubuntu 22.04&lt;/strong&gt;. The staging server: &lt;strong&gt;Node 12.22, npm 5.6, CentOS 7&lt;/strong&gt;. Three days of debugging. Three days of &lt;em&gt;"but it works on my machine!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This problem was not unique — every developer at every company was facing it. Docker was the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Hell — Real-World Examples
&lt;/h3&gt;

&lt;p&gt;The "Works on My Machine" problem is a symptom of a deeper disease: &lt;strong&gt;dependency hell&lt;/strong&gt;. Here are scenarios every developer has lived through:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;The Conflict&lt;/th&gt;
&lt;th&gt;Pain Level&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Two Python projects on same machine&lt;/td&gt;
&lt;td&gt;Project A needs Python 2.7, Project B needs Python 3.9. &lt;code&gt;pip install&lt;/code&gt; breaks both.&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm version conflict&lt;/td&gt;
&lt;td&gt;App needs npm 6 for legacy scripts, another needs npm 8. Switching breaks lockfiles.&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenSSL version mismatch&lt;/td&gt;
&lt;td&gt;Dev has OpenSSL 3.0, prod server has 1.1. Node.js crypto breaks silently.&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System library versions (glibc)&lt;/td&gt;
&lt;td&gt;Compiled on Ubuntu 22, deployed on CentOS 7. Binary won't even load.&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby gem native extensions&lt;/td&gt;
&lt;td&gt;Gem compiled against libxml2 on Mac, server has different libxml2. Segfault.&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Think of Docker like sharing a React component with &lt;code&gt;node_modules&lt;/code&gt; — not just the code, but the &lt;em&gt;exact&lt;/em&gt; node version, npm version, and OS libraries. Docker packages your application together with its entire environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Deployment Nightmares Before Docker
&lt;/h3&gt;

&lt;p&gt;Before Docker, deploying an app to a new server looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The legendary "deployment runbook" (50 pages of this)&lt;/span&gt;
ssh root@production-server-ip

&lt;span class="c"&gt;# Step 1: Install the right Node version (pray it's compatible)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_14.x | bash -
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs

&lt;span class="c"&gt;# Step 2: Hope npm is the right version&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; npm@6.14.15

&lt;span class="c"&gt;# Step 3: Clone app&lt;/span&gt;
git clone https://github.com/company/app.git /var/www/app
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/app
npm &lt;span class="nb"&gt;install&lt;/span&gt;   &lt;span class="c"&gt;# Often fails here due to native modules&lt;/span&gt;

&lt;span class="c"&gt;# Step 4: Set up environment variables (done manually, often wrong)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.100
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="c"&gt;# (Someone always forgets one. App crashes at 2 AM)&lt;/span&gt;

&lt;span class="c"&gt;# Step 5: Set up PM2 or systemd (manual, error-prone)&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pm2
pm2 start app.js &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"myapp"&lt;/span&gt;
pm2 startup  &lt;span class="c"&gt;# Different command on every distro&lt;/span&gt;

&lt;span class="c"&gt;# Step 6: Set up nginx (manual config, copy-paste errors)&lt;/span&gt;
&lt;span class="c"&gt;# ... 20 more steps ...&lt;/span&gt;

&lt;span class="c"&gt;# Total time: 4-8 hours per server&lt;/span&gt;
&lt;span class="c"&gt;# Success rate: ~60% first try&lt;/span&gt;
&lt;span class="c"&gt;# Consistency across servers: ZERO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you need to deploy to 10 servers? Do all of this 10 times — and something different will happen each time. This is called &lt;strong&gt;configuration drift&lt;/strong&gt; — over time servers diverge from each other, and no one knows why production behaves differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  The VM Tax — Resource Waste at Scale
&lt;/h3&gt;

&lt;p&gt;Companies tried solving this with Virtual Machines — one VM per application. But VMs have a massive overhead:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Each VM Consumes&lt;/th&gt;
&lt;th&gt;10 Apps = 10 VMs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;2–4 GB minimum (OS alone)&lt;/td&gt;
&lt;td&gt;20–40 GB wasted on OS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk&lt;/td&gt;
&lt;td&gt;20–40 GB per VM image&lt;/td&gt;
&lt;td&gt;200–400 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boot time&lt;/td&gt;
&lt;td&gt;1–5 minutes&lt;/td&gt;
&lt;td&gt;Can't scale fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Always-on OS processes&lt;/td&gt;
&lt;td&gt;Significant idle overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A startup with 20 microservices using VMs wastes a minimum of 40 GB RAM just for operating systems. The same 20 services in Docker containers consume 2–5 GB of overhead — a dramatic difference that translates directly to cloud cost savings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker's Shipping Container Analogy
&lt;/h3&gt;

&lt;p&gt;Before the 1950s, international shipping was chaos. Every ship had a different design, every port used different equipment, and cargo had to be repacked repeatedly. A shipment from Europe to America could take months, and costs were astronomical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In 1956, Malcolm McLean invented the standardized shipping container.&lt;/strong&gt; One fixed size (20ft/40ft), standard corners, works on any ship, any port, any truck, any train. Load once in Shanghai — unload in Rotterdam without opening the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Global trade exploded. Shipping costs dropped 90%.&lt;/p&gt;

&lt;p&gt;Docker is the shipping container of software:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shipping World&lt;/th&gt;
&lt;th&gt;Software World&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Different ships&lt;/td&gt;
&lt;td&gt;Different servers/OS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different ports&lt;/td&gt;
&lt;td&gt;Different cloud providers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cargo re-packing&lt;/td&gt;
&lt;td&gt;Re-configuring for each environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard container&lt;/td&gt;
&lt;td&gt;Docker container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load once, ship anywhere&lt;/td&gt;
&lt;td&gt;Build once, run anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;McLean standardized trade&lt;/td&gt;
&lt;td&gt;Docker standardized deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; When asked "Why do you use Docker?" in an interview — use the shipping container analogy. Then give concrete numbers: "Our deployment time went from 4 hours to 15 minutes. Configuration drift eliminated. Dev-prod parity achieved."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why Every Company Adopted Docker
&lt;/h3&gt;

&lt;p&gt;By 2026, over &lt;strong&gt;80% of cloud workloads&lt;/strong&gt; run in containers. Here's why adoption was inevitable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dev-prod parity&lt;/strong&gt; — Same container in dev, test, staging, production. No more surprises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster onboarding&lt;/strong&gt; — New developer joins? One command: &lt;code&gt;docker compose up&lt;/code&gt;. Ready in 5 minutes, not 2 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microservices enablement&lt;/strong&gt; — Each service in its own container. Scale independently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD becomes simple&lt;/strong&gt; — Build a container, test it, push to registry, deploy. Repeatable every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost reduction&lt;/strong&gt; — Pack more apps per server. AWS/GCP bills drop 40–60%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes compatibility&lt;/strong&gt; — Kubernetes orchestrates containers. You cannot use Kubernetes without containers.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Containers vs Virtual Machines
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Virtual Machines — The Apartment Building Analogy
&lt;/h3&gt;

&lt;p&gt;Think of a VM like separate apartments in a large building. Each apartment has its own kitchen, its own bathroom, its own living room. What happens in one apartment is completely invisible to the other.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Virtual Machine&lt;/strong&gt; runs a complete operating system on top of a &lt;em&gt;hypervisor&lt;/em&gt; — software that emulates physical hardware. Each VM thinks it owns the whole computer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hypervisor&lt;/strong&gt; (VMware, VirtualBox, KVM, Hyper-V) sits between hardware and VMs&lt;/li&gt;
&lt;li&gt;Each VM gets a &lt;strong&gt;full Guest OS&lt;/strong&gt; (Windows, Ubuntu, etc.) — 1–4 GB just for the OS&lt;/li&gt;
&lt;li&gt;Hardware is &lt;strong&gt;emulated&lt;/strong&gt; — virtual CPU, virtual RAM, virtual disk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong isolation&lt;/strong&gt; — a crash in VM1 doesn't affect VM2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boot time&lt;/strong&gt;: 1–5 minutes (full OS boot)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Containers — The Shared House Analogy
&lt;/h3&gt;

&lt;p&gt;Think of containers like rooms in a shared house. You share the same kitchen and water supply, but each person has their own private room and lives independently.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Container&lt;/strong&gt; shares the host OS kernel but runs in an isolated process namespace. No hardware emulation, no duplicate OS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared kernel&lt;/strong&gt; — uses the host's Linux kernel directly&lt;/li&gt;
&lt;li&gt;Each container gets an &lt;strong&gt;isolated view&lt;/strong&gt; of processes, network, filesystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No hypervisor overhead&lt;/strong&gt; — near-native performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight&lt;/strong&gt; — MBs instead of GBs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boot time&lt;/strong&gt;: Milliseconds to seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;
Containers skip the Guest OS layer that VMs require, cutting overhead from gigabytes per app to near zero.





&lt;h3&gt;
  
  
  Detailed Comparison Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Virtual Machine&lt;/th&gt;
&lt;th&gt;Container&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Startup Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1–5 minutes (full OS boot)&lt;/td&gt;
&lt;td&gt;Seconds (process start)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2–40 GB per VM image&lt;/td&gt;
&lt;td&gt;10 MB–1 GB (app + libs only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full Guest OS per VM&lt;/td&gt;
&lt;td&gt;Shares Host OS Kernel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Isolation Level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hardware-level (very strong)&lt;/td&gt;
&lt;td&gt;Process-level (strong, not absolute)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance Overhead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5–15% CPU/RAM overhead&lt;/td&gt;
&lt;td&gt;&amp;lt;1–2% overhead (near-native)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Portability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited (hypervisor-specific)&lt;/td&gt;
&lt;td&gt;Excellent (any Linux/Mac/Win with Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resource Usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heavy (duplicate OS per VM)&lt;/td&gt;
&lt;td&gt;Light (shared kernel)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Density (per server)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5–20 VMs typical&lt;/td&gt;
&lt;td&gt;50–500 containers typical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security Isolation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very strong (hardware boundary)&lt;/td&gt;
&lt;td&gt;Good (namespaces + cgroups)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Legacy apps, strong isolation, Windows workloads&lt;/td&gt;
&lt;td&gt;Microservices, CI/CD, cloud-native apps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Real-world production numbers: Server utilization jumped from 30–40% (with VMs) to 80%+ (with containers). Deployment time dropped from 45 minutes to 8 minutes. Auto-scaling: containers spin up in 3 seconds vs 4 minutes for VMs — critical during peak load events.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  When to Use VM vs Container
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use VM When...&lt;/th&gt;
&lt;th&gt;Use Container When...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Running Windows workloads on Linux host&lt;/td&gt;
&lt;td&gt;Running microservices/APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need kernel-level isolation (security-critical)&lt;/td&gt;
&lt;td&gt;CI/CD pipelines (build, test, deploy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legacy apps that need specific OS versions&lt;/td&gt;
&lt;td&gt;Stateless applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running untrusted code (multi-tenant SaaS)&lt;/td&gt;
&lt;td&gt;Development environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database servers needing dedicated resources&lt;/td&gt;
&lt;td&gt;Auto-scaling workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In enterprise environments, both are used together. VMs run Docker. In the cloud, an EC2 VM runs Docker containers on top. This is not a binary choice — they are complementary technologies.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What is Docker?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Origin Story — From DotCloud to Changing the World
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;2008:&lt;/strong&gt; Solomon Hykes co-founds DotCloud — a Platform-as-a-Service (PaaS) company in San Francisco. They're building tools to deploy customer apps on their platform, and internally they develop a container management tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;March 21, 2013:&lt;/strong&gt; PyCon, Santa Clara. Hykes gives a 5-minute lightning talk called &lt;em&gt;"The Future of Linux Containers."&lt;/em&gt; He demos Docker — their internal tool they're open-sourcing. The audience is small. The impact: &lt;strong&gt;massive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A 5-minute talk at a Python conference launched a technology that today runs in every data center in the world. That's what happens when the right solution meets the right problem at the right time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2013:&lt;/strong&gt; Docker open-sourced, goes viral on GitHub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2014:&lt;/strong&gt; Google, Microsoft, Amazon begin supporting Docker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2015:&lt;/strong&gt; Docker Inc. raises $95M, valuation $1B+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2016:&lt;/strong&gt; Docker Swarm, Docker Compose GA. Enterprise adoption explodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2017:&lt;/strong&gt; Kubernetes wins the orchestration wars, but Docker remains the build standard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2019:&lt;/strong&gt; Docker Inc. sells its enterprise business to Mirantis, refocuses on developers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2023+:&lt;/strong&gt; Docker Desktop 4.x, Docker Scout (security), AI-powered features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2026:&lt;/strong&gt; Docker is as fundamental as Git — you cannot be a backend/DevOps engineer without it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Docker vs Other Container Runtimes
&lt;/h3&gt;

&lt;p&gt;Docker is not just one tool — it is an ecosystem. But the container runtime market has other players:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Used By&lt;/th&gt;
&lt;th&gt;Key Feature&lt;/th&gt;
&lt;th&gt;Should You Learn?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developers, small teams, CI/CD&lt;/td&gt;
&lt;td&gt;Best Developer Experience (DX), most popular, huge ecosystem&lt;/td&gt;
&lt;td&gt;Yes, first priority&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;containerd&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes production clusters&lt;/td&gt;
&lt;td&gt;Lightweight production runtime, CNCF graduated project&lt;/td&gt;
&lt;td&gt;Learn after Docker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Podman&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Red Hat / Fedora users, security-conscious teams&lt;/td&gt;
&lt;td&gt;Rootless (no daemon), daemonless, Docker-compatible CLI&lt;/td&gt;
&lt;td&gt;Be aware of it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CRI-O&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OpenShift, Kubernetes&lt;/td&gt;
&lt;td&gt;Minimal footprint, built for Kubernetes CRI interface&lt;/td&gt;
&lt;td&gt;Be aware of it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;runc&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low-level, all runtimes use it&lt;/td&gt;
&lt;td&gt;OCI reference implementation, the actual container runner&lt;/td&gt;
&lt;td&gt;Deep-dive later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; When asked "What is the difference between Docker and containerd?" — say this: Docker is the full toolchain (build + run + push + compose). containerd is just the runtime that Kubernetes uses to actually run containers. Docker internally uses containerd. Think of it as: Docker = Full Car, containerd = The Engine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Docker's Place in the DevOps Stack
&lt;/h3&gt;

&lt;p&gt;Docker is the &lt;strong&gt;central connective tissue&lt;/strong&gt; of the modern DevOps pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code (React/Node/Python) → Build (docker build / Dockerfile) → Test (docker run test suite) → Registry (docker push / ECR / Docker Hub) → Deploy (ECS / Kubernetes / docker compose up)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Career Impact
&lt;/h3&gt;

&lt;p&gt;Docker knowledge significantly expands your career options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill Level&lt;/th&gt;
&lt;th&gt;Typical Role&lt;/th&gt;
&lt;th&gt;US (USD)&lt;/th&gt;
&lt;th&gt;India (INR)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without Docker&lt;/td&gt;
&lt;td&gt;Junior/Mid Developer (Frontend/Backend)&lt;/td&gt;
&lt;td&gt;$80k–120k&lt;/td&gt;
&lt;td&gt;₹15–20 LPA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker + Kubernetes&lt;/td&gt;
&lt;td&gt;DevOps / SRE (2–5 yrs exp)&lt;/td&gt;
&lt;td&gt;$120k–170k&lt;/td&gt;
&lt;td&gt;₹30–50 LPA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker + AWS + K8s + CI/CD&lt;/td&gt;
&lt;td&gt;Senior DevOps / Staff SRE (5+ yrs exp)&lt;/td&gt;
&lt;td&gt;$170k+&lt;/td&gt;
&lt;td&gt;₹50+ LPA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Ranges are rough 2026 signals and vary widely by region, company size, and cost of living.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A React/frontend background is a unique advantage in DevOps — engineers who understand both frontend containerization and backend infrastructure are in high demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker By The Numbers — 2026
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;13M+&lt;/strong&gt; images on Docker Hub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;13B+&lt;/strong&gt; pulls per month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20M+&lt;/strong&gt; Docker Desktop users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;80%&lt;/strong&gt; of cloud workloads containerized&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Containerization Concepts — The Linux Magic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Foundation — Why Linux?
&lt;/h3&gt;

&lt;p&gt;Docker's magic comes from three specific Linux kernel features. Understanding them is what separates a senior engineer from a beginner — and this knowledge is invaluable in interviews when asked "How does Docker work internally?"&lt;/p&gt;

&lt;p&gt;Docker containers are not magic — they're &lt;strong&gt;three Linux kernel features&lt;/strong&gt; working together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Namespaces&lt;/strong&gt; — What a container can &lt;em&gt;see&lt;/em&gt; (isolation of view)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cgroups&lt;/strong&gt; — What a container can &lt;em&gt;use&lt;/em&gt; (resource limits)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Union Filesystem (OverlayFS)&lt;/strong&gt; — How a container's &lt;em&gt;filesystem&lt;/em&gt; is built&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Linux Namespaces — The Isolation Magic
&lt;/h3&gt;

&lt;p&gt;A namespace makes a container feel like it is the only process on the machine — like an actor on a film set who is made to feel they are actually in a royal palace, when they are actually standing on a soundstage.&lt;/p&gt;

&lt;p&gt;Linux has &lt;strong&gt;7 types of namespaces&lt;/strong&gt;. Docker uses 6 of them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PID Namespace — Process ID Isolation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The container believes it has its own process tree. Container's PID 1 = Host's PID 5678.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inside container: sees its own process tree&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ps aux
PID   USER  CMD
1     root  node server.js   ← thinks it&lt;span class="s1"&gt;'s PID 1!
12    root  /bin/sh

# On host: sees the real PID
$ ps aux | grep node
5678  root  node server.js   ← actually PID 5678
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The container cannot see parent processes. Completely isolated process tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NET Namespace — Network Isolation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each container gets its own network interfaces, IP address, routing table, and firewall rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container 1: has its own IP&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;container1 ip addr
172.17.0.2/16   ← container&lt;span class="s1"&gt;'s IP

# Container 2: different IP
$ docker exec container2 ip addr
172.17.0.3/16   ← different IP, same host

# Host: sees docker0 bridge
$ ip addr show docker0
172.17.0.1/16   ← bridge connects them
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;MNT Namespace — Filesystem Isolation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The container has its own filesystem view. It appears to have a complete Linux filesystem — &lt;code&gt;/etc&lt;/code&gt;, &lt;code&gt;/usr&lt;/code&gt;, &lt;code&gt;/var&lt;/code&gt; all separate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container sees its own filesystem&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;mycontainer &lt;span class="nb"&gt;ls&lt;/span&gt; /
bin  dev  etc  home  lib  proc
root  sys  tmp  usr  var  app

&lt;span class="c"&gt;# Host files are NOT visible in container&lt;/span&gt;
&lt;span class="c"&gt;# (unless explicitly mounted with -v)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;UTS Namespace — Hostname Isolation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each container can have its own hostname, independent of the host machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set custom hostname for container&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp-prod ubuntu &lt;span class="nb"&gt;hostname
&lt;/span&gt;myapp-prod

&lt;span class="c"&gt;# Host machine sees its own hostname&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;hostname
&lt;/span&gt;prod-server-01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;USER Namespace — User ID Mapping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Maps the container's root user to an unprivileged host user. Container "root" is NOT host root — important for security!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container root → host user 1000&lt;/span&gt;
&lt;span class="c"&gt;# Even if container is "root",&lt;/span&gt;
&lt;span class="c"&gt;# on host it's unprivileged UID 1000&lt;/span&gt;
&lt;span class="c"&gt;# Security: container escape doesn't&lt;/span&gt;
&lt;span class="c"&gt;# give attacker host root access&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IPC Namespace — Inter-Process Communication Isolation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Containers cannot access each other's shared memory, semaphores, or message queues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Share IPC namespace between containers&lt;/span&gt;
&lt;span class="c"&gt;# (for high-performance apps like Redis)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--ipc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;container:redis-container my-app

&lt;span class="c"&gt;# Default: isolated IPC per container&lt;/span&gt;
&lt;span class="c"&gt;# No shared memory leaks between apps&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cgroups — Resource Control
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Control Groups (cgroups)&lt;/strong&gt; control &lt;em&gt;how much&lt;/em&gt; resource a container can use. If namespaces define what a container can see, cgroups define how much it can consume.&lt;/p&gt;

&lt;p&gt;Think of cgroups as a &lt;strong&gt;resource budget&lt;/strong&gt; per container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Memory limit: container cannot use more than 512MB&lt;/span&gt;
docker run &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m nginx

&lt;span class="c"&gt;# CPU limit: container gets max 50% of one CPU core&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 nginx

&lt;span class="c"&gt;# Both together (production-ready)&lt;/span&gt;
docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory-swap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpu-shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512 &lt;span class="se"&gt;\&lt;/span&gt;
  nginx

&lt;span class="c"&gt;# I/O limits (read/write speed)&lt;/span&gt;
docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--device-read-bps&lt;/span&gt; /dev/sda:100mb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--device-write-bps&lt;/span&gt; /dev/sda:50mb &lt;span class="se"&gt;\&lt;/span&gt;
  nginx

&lt;span class="c"&gt;# See current container resource usage&lt;/span&gt;
docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt;

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# CONTAINER   CPU %   MEM USAGE / LIMIT    NET I/O&lt;/span&gt;
&lt;span class="c"&gt;# nginx       0.1%    12MiB / 512MiB       1.2kB / 0B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cgroup Subsystem&lt;/th&gt;
&lt;th&gt;What It Controls&lt;/th&gt;
&lt;th&gt;Docker Flag&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Max RAM usage, OOM killer behavior&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--memory=512m&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPU time allocation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--cpus=0.5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cpuset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which CPU cores to use&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--cpuset-cpus=0,1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;blkio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Block I/O limits (disk read/write)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--device-read-bps&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;net_cls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network bandwidth tagging&lt;/td&gt;
&lt;td&gt;(via tc/iptables)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pids&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Max number of processes in container&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--pids-limit=100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Always set memory and CPU limits on every production container. Without limits, a runaway container can crash the entire host server — the "noisy neighbour" problem. During peak traffic, if one container hogs CPU, all other containers will slow down. Limits protect all containers on the host.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Union Filesystem — OverlayFS
&lt;/h3&gt;

&lt;p&gt;A Docker image is made of &lt;strong&gt;read-only layers&lt;/strong&gt;. When you run a container, Docker adds a thin &lt;strong&gt;writable layer&lt;/strong&gt; on top. This is OverlayFS (Overlay Filesystem).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example Dockerfile - creates layers&lt;/span&gt;
FROM ubuntu:22.04            &lt;span class="c"&gt;# Layer 1: Base Ubuntu (~70MB)&lt;/span&gt;
RUN apt-get update           &lt;span class="c"&gt;# Layer 2: Package index (~20MB)&lt;/span&gt;
RUN apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl  &lt;span class="c"&gt;# Layer 3: curl binary (~5MB)&lt;/span&gt;
COPY app.js /app/            &lt;span class="c"&gt;# Layer 4: Your app (~1MB)&lt;/span&gt;
CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;, &lt;span class="s2"&gt;"app.js"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;       &lt;span class="c"&gt;# Layer 5: Metadata only&lt;/span&gt;

&lt;span class="c"&gt;# See the layers of an image&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;history &lt;/span&gt;my-node-app
IMAGE         CREATED      SIZE     COMMENT
a1b2c3d4      2 hours ago  1.2MB    COPY app.js
f5e6d7c8      2 hours ago  5.1MB    apt-get &lt;span class="nb"&gt;install &lt;/span&gt;curl
g9h0i1j2      2 hours ago  22MB     apt-get update
ubuntu:22.04  2 weeks ago  72MB     base layer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Copy-on-Write (CoW) — The Secret Sauce&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a container &lt;strong&gt;modifies&lt;/strong&gt; a file from a read-only layer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Docker &lt;em&gt;copies&lt;/em&gt; that file to the writable layer&lt;/li&gt;
&lt;li&gt;Makes the modification in the writable layer&lt;/li&gt;
&lt;li&gt;The original read-only layer is &lt;strong&gt;untouched&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;100 containers sharing the same image = base layers shared, only diffs stored&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If 100 containers are using the same nginx image, there is still only one copy on disk. 100x efficiency!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See OverlayFS in action on Linux host&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker inspect mycontainer | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 10 GraphDriver
&lt;span class="s2"&gt;"GraphDriver"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Data"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"LowerDir"&lt;/span&gt;: &lt;span class="s2"&gt;"/var/lib/docker/overlay2/abc123/diff:
                 /var/lib/docker/overlay2/def456/diff"&lt;/span&gt;,
    &lt;span class="s2"&gt;"MergedDir"&lt;/span&gt;: &lt;span class="s2"&gt;"/var/lib/docker/overlay2/xyz789/merged"&lt;/span&gt;,
    &lt;span class="s2"&gt;"UpperDir"&lt;/span&gt;:  &lt;span class="s2"&gt;"/var/lib/docker/overlay2/xyz789/diff"&lt;/span&gt;,
    &lt;span class="s2"&gt;"WorkDir"&lt;/span&gt;:   &lt;span class="s2"&gt;"/var/lib/docker/overlay2/xyz789/work"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;# LowerDir = read-only image layers (stacked)&lt;/span&gt;
&lt;span class="c"&gt;# UpperDir = writable container layer (your changes)&lt;/span&gt;
&lt;span class="c"&gt;# MergedDir = what container sees (unified view)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (Senior Engineer Interview):&lt;/strong&gt; "Both Docker containers and VMs provide isolation — so why are containers considered less secure?" Answer: VMs provide hardware-level isolation (hypervisor = hard boundary). Containers only use Linux kernel features — if there is a kernel vulnerability (like Dirty COW, runc CVE), container escape is possible. This is why multi-tenant environments running untrusted code prefer VMs or gVisor/Kata Containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip (Quick Summary):&lt;/strong&gt; Docker container = Linux process + Namespaces (isolation of view: can't see others' processes, network, files) + Cgroups (resource limits: max CPU/RAM) + OverlayFS (layered filesystem: shared base + writable top). Everything else — Dockerfile, docker-compose, Kubernetes — is built on top of these three.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Architecture Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Big Picture
&lt;/h3&gt;

&lt;p&gt;Docker follows a client-server architecture. When you type &lt;code&gt;docker run nginx&lt;/code&gt;, a lot is happening under the hood — think of it as an assembly line where each component performs a specific job.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;
The Docker CLI is a thin REST client; all heavy lifting happens inside the Docker daemon, which coordinates containerd and runc to run containers and communicates with registries for image storage.





&lt;h3&gt;
  
  
  1. Docker Client (CLI)
&lt;/h3&gt;

&lt;p&gt;The Docker Client is the tool you use — the &lt;code&gt;docker&lt;/code&gt; command. When you type &lt;code&gt;docker run nginx&lt;/code&gt;, the client only translates CLI arguments into a REST API call and sends it to the Docker Daemon. The client itself does nothing else — it is just a translator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Docker CLI is just making REST API calls to the daemon&lt;/span&gt;
&lt;span class="c"&gt;# You can curl the Docker socket directly&lt;/span&gt;

&lt;span class="c"&gt;# List running containers (same as docker ps)&lt;/span&gt;
curl &lt;span class="nt"&gt;--unix-socket&lt;/span&gt; /var/run/docker.sock http://localhost/v1.43/containers/json

&lt;span class="c"&gt;# Start a container via API&lt;/span&gt;
curl &lt;span class="nt"&gt;--unix-socket&lt;/span&gt; /var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"Image": "nginx"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://localhost/v1.43/containers/create

&lt;span class="c"&gt;# Check Docker version via API&lt;/span&gt;
curl &lt;span class="nt"&gt;--unix-socket&lt;/span&gt; /var/run/docker.sock http://localhost/v1.43/version | jq

&lt;span class="c"&gt;# See all API endpoints&lt;/span&gt;
curl &lt;span class="nt"&gt;--unix-socket&lt;/span&gt; /var/run/docker.sock http://localhost/v1.43/_ping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; You can control a remote machine's Docker: &lt;code&gt;export DOCKER_HOST=tcp://192.168.1.100:2376&lt;/code&gt;, then run normal docker commands. In production, always use TLS (port 2376), never plain port 2375!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Docker Daemon (dockerd)
&lt;/h3&gt;

&lt;p&gt;The Docker Daemon (dockerd) is the background process that actually does the work. It accepts REST API requests and manages containers, images, networks, and volumes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default Socket&lt;/td&gt;
&lt;td&gt;&lt;code&gt;unix:///var/run/docker.sock&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCP (TLS)&lt;/td&gt;
&lt;td&gt;Port 2376 (production)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCP (No TLS)&lt;/td&gt;
&lt;td&gt;Port 2375 (NEVER in production!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config File&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;journalctl -u docker.service&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restart&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo systemctl restart docker&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Docker Registry
&lt;/h3&gt;

&lt;p&gt;Registry is where images are stored. Docker Hub is the default public registry, but enterprises use private registries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker Hub&lt;/strong&gt; — Default public registry. Free tier available. &lt;code&gt;docker.io/library/nginx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS ECR&lt;/strong&gt; — Amazon Elastic Container Registry. IAM integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCR / GAR&lt;/strong&gt; — Google Container/Artifact Registry. Seamless with GKE.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GHCR&lt;/strong&gt; — GitHub Container Registry. Tight CI/CD integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harbor&lt;/strong&gt; — Open-source enterprise registry. Self-hosted. Vulnerability scanning built-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artifactory&lt;/strong&gt; — JFrog's universal artifact manager. Docker + Maven + npm in one place.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. containerd — High-Level Runtime
&lt;/h3&gt;

&lt;p&gt;containerd is a CNCF project that sits between the Docker Daemon and actual containers. It manages image pulling, storage, and container lifecycle. Kubernetes also uses containerd directly (without Docker).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# containerd has its own CLI: ctr&lt;/span&gt;
&lt;span class="c"&gt;# Note: operates in a different namespace from Docker&lt;/span&gt;

&lt;span class="c"&gt;# List images in containerd&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ctr images &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# List containers&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ctr containers &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# Pull image directly via containerd&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ctr images pull docker.io/library/nginx:latest

&lt;span class="c"&gt;# Run container via containerd (low-level)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ctr run docker.io/library/nginx:latest mynginx

&lt;span class="c"&gt;# containerd socket&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /run/containerd/containerd.sock

&lt;span class="c"&gt;# Check containerd status&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status containerd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Kubernetes v1.24+ removed Docker support. Kubernetes now talks directly to containerd via CRI (Container Runtime Interface). Docker's "removal" was only the removal of the Docker daemon — containers still run the same way because containerd and runc are the same underneath.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. runc — Low-Level Runtime
&lt;/h3&gt;

&lt;p&gt;runc is the reference implementation of the OCI Runtime Specification. It directly makes Linux kernel syscalls — creates namespaces, sets up cgroups, and starts the process. It is the actual tool that makes a "container" exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# runc version check&lt;/span&gt;
runc &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;span class="c"&gt;# runc is not normally used directly&lt;/span&gt;
&lt;span class="c"&gt;# But for understanding: containerd calls runc like this:&lt;/span&gt;
&lt;span class="c"&gt;# runc create --bundle /path/to/bundle container_id&lt;/span&gt;
&lt;span class="c"&gt;# runc start container_id&lt;/span&gt;
&lt;span class="c"&gt;# runc delete container_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Complete Flow: docker run nginx
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;You type &lt;code&gt;docker run nginx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docker CLI → REST API call → Docker daemon (via /var/run/docker.sock)&lt;/li&gt;
&lt;li&gt;dockerd checks: is the image available locally? No → pull from Registry&lt;/li&gt;
&lt;li&gt;dockerd → tells containerd to start the container&lt;/li&gt;
&lt;li&gt;containerd → spawns a containerd-shim process&lt;/li&gt;
&lt;li&gt;containerd-shim → calls runc&lt;/li&gt;
&lt;li&gt;runc → makes Linux kernel syscalls (namespaces + cgroups setup)&lt;/li&gt;
&lt;li&gt;Container process (nginx) starts&lt;/li&gt;
&lt;li&gt;runc exits; containerd-shim stays alive (for I/O)&lt;/li&gt;
&lt;li&gt;Container keeps running even on daemon restart&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Docker Installation on Ubuntu 24.04 (Complete Guide)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites Check
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Check if Docker is already installed before starting. If an old version exists, remove it first: &lt;code&gt;sudo apt remove docker docker-engine docker.io containerd runc&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# System info check&lt;/span&gt;
&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
lsb_release &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;whoami&lt;/span&gt;

&lt;span class="c"&gt;# Check if Docker already installed&lt;/span&gt;
docker &lt;span class="nt"&gt;--version&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Docker not installed"&lt;/span&gt;

&lt;span class="c"&gt;# Remove old versions if they exist&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt remove &lt;span class="nt"&gt;-y&lt;/span&gt; docker docker-engine docker.io containerd runc 2&amp;gt;/dev/null
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt autoremove &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: System Update
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# If the kernel was updated, reboot (optional but recommended)&lt;/span&gt;
&lt;span class="c"&gt;# sudo reboot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Install Prerequisites
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ca-certificates &lt;span class="se"&gt;\&lt;/span&gt;
  curl &lt;span class="se"&gt;\&lt;/span&gt;
  gnupg &lt;span class="se"&gt;\&lt;/span&gt;
  lsb-release

&lt;span class="c"&gt;# Verify install&lt;/span&gt;
curl &lt;span class="nt"&gt;--version&lt;/span&gt;
gpg &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Add Docker's Official GPG Key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create keyrings directory&lt;/span&gt;
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings

&lt;span class="c"&gt;# Download Docker's GPG key and convert to binary format&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.gpg

&lt;span class="c"&gt;# Set read permissions&lt;/span&gt;
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.gpg

&lt;span class="c"&gt;# Verify key&lt;/span&gt;
gpg &lt;span class="nt"&gt;--show-keys&lt;/span&gt; /etc/apt/keyrings/docker.gpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The GPG key ensures packages come from Docker's official server and have not been intercepted. This protects against man-in-the-middle attacks. Always verify packages before installing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 4: Add Docker Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"deb [arch="&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;" signed-by=/etc/apt/keyrings/docker.gpg] &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  "&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;" stable"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="c"&gt;# Verify repository was added&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/apt/sources.list.d/docker.list

&lt;span class="c"&gt;# Expected output:&lt;/span&gt;
&lt;span class="c"&gt;# deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg]&lt;/span&gt;
&lt;span class="c"&gt;#   https://download.docker.com/linux/ubuntu noble stable&lt;/span&gt;

&lt;span class="c"&gt;# Update apt cache with new repository&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Install Docker Engine
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  docker-ce &lt;span class="se"&gt;\&lt;/span&gt;
  docker-ce-cli &lt;span class="se"&gt;\&lt;/span&gt;
  containerd.io &lt;span class="se"&gt;\&lt;/span&gt;
  docker-buildx-plugin &lt;span class="se"&gt;\&lt;/span&gt;
  docker-compose-plugin

&lt;span class="c"&gt;# Package breakdown:&lt;/span&gt;
&lt;span class="c"&gt;# docker-ce              → Docker Community Edition (daemon)&lt;/span&gt;
&lt;span class="c"&gt;# docker-ce-cli          → Docker CLI (the 'docker' command)&lt;/span&gt;
&lt;span class="c"&gt;# containerd.io          → containerd runtime&lt;/span&gt;
&lt;span class="c"&gt;# docker-buildx-plugin   → BuildKit for advanced image building&lt;/span&gt;
&lt;span class="c"&gt;# docker-compose-plugin  → Docker Compose v2 (docker compose, not docker-compose)&lt;/span&gt;

&lt;span class="c"&gt;# To install a specific version:&lt;/span&gt;
&lt;span class="c"&gt;# List available versions&lt;/span&gt;
apt-cache madison docker-ce

&lt;span class="c"&gt;# Install specific version&lt;/span&gt;
&lt;span class="c"&gt;# sudo apt install docker-ce=5:26.0.0-1~ubuntu.24.04~noble \&lt;/span&gt;
&lt;span class="c"&gt;#   docker-ce-cli=5:26.0.0-1~ubuntu.24.04~noble \&lt;/span&gt;
&lt;span class="c"&gt;#   containerd.io docker-buildx-plugin docker-compose-plugin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Verify Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run hello-world

&lt;span class="c"&gt;# Expected output:&lt;/span&gt;
&lt;span class="c"&gt;# Hello from Docker!&lt;/span&gt;
&lt;span class="c"&gt;# This message shows that your installation appears to be working correctly.&lt;/span&gt;

&lt;span class="c"&gt;# Service status check&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status containerd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7: Post-Installation — Run Without sudo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create 'docker' group (usually already exists)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;groupadd docker 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Group already exists"&lt;/span&gt;

&lt;span class="c"&gt;# Add current user to docker group&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;

&lt;span class="c"&gt;# Apply group changes (no logout/login needed for this session)&lt;/span&gt;
newgrp docker

&lt;span class="c"&gt;# Verify — run docker without sudo&lt;/span&gt;
docker run hello-world

&lt;span class="c"&gt;# Permanent verification&lt;/span&gt;
&lt;span class="nb"&gt;groups&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="c"&gt;# Output should include 'docker'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Adding a user to the docker group is practically equivalent to giving root access. Through the Docker socket (&lt;code&gt;/var/run/docker.sock&lt;/code&gt;), anyone can mount a volume and access the host filesystem. Add users carefully on production servers. Rootless Docker is a more secure option.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 8: Enable Docker on Boot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;docker.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;containerd.service

&lt;span class="c"&gt;# Manual start/stop/restart&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart docker

&lt;span class="c"&gt;# View logs&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; docker.service &lt;span class="nt"&gt;-f&lt;/span&gt;      &lt;span class="c"&gt;# Live logs&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; docker.service &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"1 hour ago"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Post-Installation Verification
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Version info&lt;/span&gt;
docker &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# Docker version 26.1.4, build 5650f9b&lt;/span&gt;

docker compose version
&lt;span class="c"&gt;# Docker Compose version v2.27.1&lt;/span&gt;

&lt;span class="c"&gt;# System info (detailed)&lt;/span&gt;
docker info

&lt;span class="c"&gt;# Test container&lt;/span&gt;
docker run hello-world

&lt;span class="c"&gt;# Interactive ubuntu container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; ubuntu bash

&lt;span class="c"&gt;# Nginx web server test&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx
curl http://localhost:8080

&lt;span class="c"&gt;# Cleanup&lt;/span&gt;
docker stop &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Installation Errors &amp;amp; Fixes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;permission denied while trying to connect&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User not in docker group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo usermod -aG docker $USER &amp;amp;&amp;amp; newgrp docker&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cannot connect to the Docker daemon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dockerd not running&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo systemctl start docker&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dial unix /var/run/docker.sock: connect: no such file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Docker not installed or not started&lt;/td&gt;
&lt;td&gt;Install Docker OR &lt;code&gt;sudo systemctl start docker&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Failed to connect to bus: No such file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;systemd not available (WSL/container)&lt;/td&gt;
&lt;td&gt;Logout and login again, or &lt;code&gt;sudo service docker start&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;E: Package 'docker-ce' has no installation candidate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Repository not added&lt;/td&gt;
&lt;td&gt;Redo steps 3-4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;toomanyrequests&lt;/code&gt; from Docker Hub&lt;/td&gt;
&lt;td&gt;Rate limit hit (unauthenticated)&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;docker login&lt;/code&gt; or use a mirror&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Docker Rootless Mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install prerequisites&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; uidmap

&lt;span class="c"&gt;# Install rootless Docker (as regular user, NOT sudo)&lt;/span&gt;
dockerd-rootless-setuptool.sh &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Set environment variables&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unix://&lt;span class="nv"&gt;$XDG_RUNTIME_DIR&lt;/span&gt;/docker.sock
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;

&lt;span class="c"&gt;# Add to ~/.bashrc for persistence&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH=$HOME/bin:$PATH'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc

&lt;span class="c"&gt;# Enable service (user-level systemd)&lt;/span&gt;
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;docker
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start docker

&lt;span class="c"&gt;# Verify&lt;/span&gt;
docker info | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; rootless
&lt;span class="c"&gt;# Should show: rootless: true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In security-critical environments, use rootless Docker or Podman. In rootless mode, the impact of container breakout attacks is drastically reduced because the daemon is not root. In Kubernetes environments, configure &lt;code&gt;securityContext&lt;/code&gt; with non-root users.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Installation on RHEL / CentOS / Rocky Linux
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Install yum-utils&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; yum-utils

&lt;span class="c"&gt;# Step 2: Add Docker repository&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum-config-manager &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--add-repo&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://download.docker.com/linux/centos/docker-ce.repo

&lt;span class="c"&gt;# Step 3: Install Docker&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  docker-ce &lt;span class="se"&gt;\&lt;/span&gt;
  docker-ce-cli &lt;span class="se"&gt;\&lt;/span&gt;
  containerd.io &lt;span class="se"&gt;\&lt;/span&gt;
  docker-buildx-plugin &lt;span class="se"&gt;\&lt;/span&gt;
  docker-compose-plugin

&lt;span class="c"&gt;# Step 4: Start and enable&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;docker

&lt;span class="c"&gt;# Step 5: Post-install&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
newgrp docker

&lt;span class="c"&gt;# Verify&lt;/span&gt;
docker run hello-world

&lt;span class="c"&gt;# Note: On RHEL 9+, podman is preferred (Docker-compatible CLI)&lt;/span&gt;
&lt;span class="c"&gt;# podman run hello-world  # Same commands, daemonless!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Docker Engine Components — Under the Hood
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Component Stack
&lt;/h3&gt;

&lt;p&gt;The Docker Engine follows this layered stack from top to bottom:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;docker CLI&lt;/strong&gt; — &lt;code&gt;docker run / build / push / pull&lt;/code&gt; (makes REST API calls)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dockerd — Docker Daemon&lt;/strong&gt; — REST API | &lt;code&gt;/var/run/docker.sock&lt;/code&gt; | Port 2376 (TLS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;containerd&lt;/strong&gt; — Image management | Container lifecycle | &lt;code&gt;/run/containerd/containerd.sock&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;containerd-shim&lt;/strong&gt; — Daemonless container I/O | stdout/stderr relay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;runc (OCI Runtime)&lt;/strong&gt; — syscalls to kernel: &lt;code&gt;clone()&lt;/code&gt;, &lt;code&gt;unshare()&lt;/code&gt;, &lt;code&gt;setns()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux Kernel&lt;/strong&gt; — namespaces (pid, net, mnt, uts, ipc) | cgroups (cpu, memory, io)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container Process&lt;/strong&gt; — Your app running as PID 1 inside container&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  dockerd — Docker Daemon Deep Dive
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View dockerd process&lt;/span&gt;
ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;dockerd
&lt;span class="c"&gt;# root  1234  ... /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock&lt;/span&gt;

&lt;span class="c"&gt;# Socket files&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/run/docker.sock             &lt;span class="c"&gt;# Unix socket (default)&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /run/containerd/containerd.sock  &lt;span class="c"&gt;# containerd socket&lt;/span&gt;

&lt;span class="c"&gt;# dockerd config file&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/docker/daemon.json

&lt;span class="c"&gt;# Restart dockerd (containers will NOT stop!)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart docker

&lt;span class="c"&gt;# View dockerd logs&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; docker.service &lt;span class="nt"&gt;-n&lt;/span&gt; 50 &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; docker.service &lt;span class="nt"&gt;-f&lt;/span&gt;  &lt;span class="c"&gt;# Live tail&lt;/span&gt;

&lt;span class="c"&gt;# Docker events stream&lt;/span&gt;
docker events  &lt;span class="c"&gt;# Real-time events: container start/stop/die, image pull, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;daemon.json — Production Configuration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"log-driver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json-file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"log-opts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max-size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max-file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default-address-pools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"172.17.0.0/12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"storage-driver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"overlay2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registry-mirrors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://mirror.gcr.io"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"insecure-registries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"live-restore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max-concurrent-downloads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"max-concurrent-uploads"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metrics-addr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0:9323"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"experimental"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (Production Must-Haves):&lt;/strong&gt; &lt;code&gt;log-driver + log-opts&lt;/code&gt;: By default Docker logs can consume unlimited disk. Set &lt;code&gt;max-size: 100m&lt;/code&gt; and &lt;code&gt;max-file: 3&lt;/code&gt; — total 300MB max per container. &lt;code&gt;live-restore: true&lt;/code&gt;: Containers will not stop when the Docker daemon restarts. Critical for zero-downtime daemon updates! &lt;code&gt;storage-driver: overlay2&lt;/code&gt;: Best performance on modern kernels. Also the default on Ubuntu.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  containerd — Detailed
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inspect containerd directly&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ctr version
&lt;span class="nb"&gt;sudo &lt;/span&gt;ctr namespaces &lt;span class="nb"&gt;ls&lt;/span&gt;         &lt;span class="c"&gt;# Docker uses 'moby' namespace&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ctr &lt;span class="nt"&gt;-n&lt;/span&gt; moby images &lt;span class="nb"&gt;ls&lt;/span&gt;     &lt;span class="c"&gt;# Docker images in containerd&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ctr &lt;span class="nt"&gt;-n&lt;/span&gt; moby containers &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="c"&gt;# Running containers&lt;/span&gt;

&lt;span class="c"&gt;# containerd config&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/containerd/config.toml

&lt;span class="c"&gt;# Restart containerd (Docker containers will temporarily stop!)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart containerd

&lt;span class="c"&gt;# In Kubernetes clusters (containerd directly)&lt;/span&gt;
&lt;span class="c"&gt;# crictl images         # CRI-compatible CLI&lt;/span&gt;
&lt;span class="c"&gt;# crictl ps             # Running containers&lt;/span&gt;
&lt;span class="c"&gt;# crictl inspect &amp;lt;id&amp;gt;   # Container details&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (containerd-shim):&lt;/strong&gt; containerd-shim is an intermediate process spawned for each container. Its job: (1) relay stdin/stdout/stderr, (2) report exit status, (3) keep the container alive even if the daemon crashes. This is why &lt;code&gt;live-restore: true&lt;/code&gt; works!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  OCI — Open Container Initiative
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Specification&lt;/th&gt;
&lt;th&gt;Defines&lt;/th&gt;
&lt;th&gt;Implemented By&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Image Spec&lt;/td&gt;
&lt;td&gt;Image format: layers, manifest, config JSON&lt;/td&gt;
&lt;td&gt;Docker, containerd, Buildah&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime Spec&lt;/td&gt;
&lt;td&gt;How to run a container: config.json + rootfs&lt;/td&gt;
&lt;td&gt;runc, crun, kata-containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distribution Spec&lt;/td&gt;
&lt;td&gt;How images are pushed/pulled from registries&lt;/td&gt;
&lt;td&gt;Docker Hub, ECR, GCR, Harbor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip (Interview Question):&lt;/strong&gt; &lt;strong&gt;Q: "What is the difference between Docker and containerd?"&lt;/strong&gt; &lt;strong&gt;A:&lt;/strong&gt; Docker is a complete platform (CLI + daemon + build tools + compose). containerd is just a container runtime — lightweight, CRI-compatible, perfect for Kubernetes. Docker internally uses containerd. Kubernetes bypasses the Docker daemon and talks directly to containerd (since v1.24).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Understanding Docker Images
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Image — The Blueprint
&lt;/h3&gt;

&lt;p&gt;An image is an immutable blueprint. Think of it this way: a &lt;code&gt;class Component extends React.Component&lt;/code&gt; — you don't directly "run" a class, you create an instance. Similarly, Image = Class, Container = Instance. An image is built once, then thousands of containers can be created from it. The image itself never changes.&lt;/p&gt;

&lt;p&gt;Technically, a Docker image is a read-only filesystem snapshot + metadata (env vars, exposed ports, entry command), organized in layers — each layer representing an incremental change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Layers — The Onion Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────┐
│  Container Layer — WRITABLE             │  ← New files, modifications go here.
│  New files, modifications go here.      │    Deleted when container stops.
│  Deleted when container stops.          │
├─────────────────────────────────────────┤
│           ↑ WRITABLE / READ-ONLY ↓      │
├─────────────────────────────────────────┤
│  Layer 4: COPY . /app                   │  ← Your application source code (~5 MB)
├─────────────────────────────────────────┤
│  Layer 3: RUN npm install               │  ← node_modules directory (~80 MB)
├─────────────────────────────────────────┤
│  Layer 2: WORKDIR /app                  │  ← Creates /app directory (metadata only)
├─────────────────────────────────────────┤
│  Layer 1: FROM node:18-alpine           │  ← Base OS + Node.js runtime (~120 MB,
│                                         │    shared with other node images)
└─────────────────────────────────────────┘
  OverlayFS merges all layers → appears as single filesystem to container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; OverlayFS is a Linux kernel filesystem driver. It merges multiple read-only layers and one read-write layer into a single unified filesystem. The container sees a normal filesystem, but it is actually a stack of layers. Copy-on-Write is used — when a container modifies a file, it is first copied from the read-only layer to the read-write layer (upperdir), then modified.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Image Manifest and Digest
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View image manifest (JSON format)&lt;/span&gt;
docker manifest inspect nginx:latest

&lt;span class="c"&gt;# Image digest — immutable SHA256 reference&lt;/span&gt;
docker images &lt;span class="nt"&gt;--digests&lt;/span&gt; nginx
&lt;span class="c"&gt;# REPOSITORY   TAG       DIGEST                                                    IMAGE ID&lt;/span&gt;
&lt;span class="c"&gt;# nginx        latest    sha256:a484819eb60...                                     e784f4560448&lt;/span&gt;

&lt;span class="c"&gt;# Pull by digest (PRODUCTION BEST PRACTICE)&lt;/span&gt;
&lt;span class="c"&gt;# Tags can change (latest is mutable), digests never change!&lt;/span&gt;
docker pull nginx@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c

&lt;span class="c"&gt;# Tags are mutable — this is dangerous:&lt;/span&gt;
docker pull nginx:latest   &lt;span class="c"&gt;# Today pulls one version, tomorrow may pull another&lt;/span&gt;

&lt;span class="c"&gt;# Safe production pull (pinned digest):&lt;/span&gt;
docker pull nginx@sha256:exact_digest_from_registry

&lt;span class="c"&gt;# Get digest of a local image&lt;/span&gt;
docker inspect nginx &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{{index .RepoDigests 0}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; The &lt;code&gt;:latest&lt;/code&gt; tag is a moving target. If you pull today you get 1.25; if CI/CD pulls tomorrow it might get 1.26 — with breaking changes. In production, always pin semantic versions: &lt;code&gt;nginx:1.25.3-alpine&lt;/code&gt; or use digests. &lt;code&gt;:latest&lt;/code&gt; is only acceptable for local development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Image Storage on Disk
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Where Docker images are stored on disk&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; /var/lib/docker/

&lt;span class="c"&gt;# Image layers (overlay2 storage driver)&lt;/span&gt;
&lt;span class="nb"&gt;sudo ls&lt;/span&gt; /var/lib/docker/overlay2/
&lt;span class="c"&gt;# Each directory is a layer&lt;/span&gt;

&lt;span class="c"&gt;# Image metadata&lt;/span&gt;
&lt;span class="nb"&gt;sudo ls&lt;/span&gt; /var/lib/docker/image/overlay2/imagedb/content/sha256/
&lt;span class="c"&gt;# Each file is an image manifest/config&lt;/span&gt;

&lt;span class="c"&gt;# Disk usage breakdown — important for DevOps!&lt;/span&gt;
docker system &lt;span class="nb"&gt;df&lt;/span&gt;
&lt;span class="c"&gt;# TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE&lt;/span&gt;
&lt;span class="c"&gt;# Images          15        3         2.1GB     1.8GB (85%)&lt;/span&gt;
&lt;span class="c"&gt;# Containers      5         2         45MB      23MB (51%)&lt;/span&gt;
&lt;span class="c"&gt;# Local Volumes   8         3         560MB     300MB (53%)&lt;/span&gt;
&lt;span class="c"&gt;# Build Cache     0         0         0B        0B&lt;/span&gt;

&lt;span class="c"&gt;# Detailed breakdown&lt;/span&gt;
docker system &lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;

&lt;span class="c"&gt;# View layer sharing (images share base layers)&lt;/span&gt;
docker &lt;span class="nb"&gt;history &lt;/span&gt;nginx:latest &lt;span class="nt"&gt;--no-trunc&lt;/span&gt;
docker &lt;span class="nb"&gt;history &lt;/span&gt;nginx:alpine &lt;span class="nt"&gt;--no-trunc&lt;/span&gt;
&lt;span class="c"&gt;# Common layers are shared — only one copy on disk!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Image Tags vs Digests vs IDs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Mutable?&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Image ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;e784f4560448&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No (content-addressed)&lt;/td&gt;
&lt;td&gt;Local reference, scripting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tag&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nginx:1.25.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes (can be moved)&lt;/td&gt;
&lt;td&gt;Human-readable, dev/staging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nginx@sha256:abc...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No (cryptographic)&lt;/td&gt;
&lt;td&gt;Production deployments, security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;latest tag&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nginx:latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes (frequently moves)&lt;/td&gt;
&lt;td&gt;Local dev ONLY, never production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip (Interview Question):&lt;/strong&gt; &lt;strong&gt;Q: "What is the difference between a Docker image and a container?"&lt;/strong&gt; &lt;strong&gt;A:&lt;/strong&gt; An image is a read-only blueprint/template — a static filesystem snapshot. A container is a running instance of that image, with a writable layer added on top. You can create thousands of containers from one image. When a container is stopped, the writable layer is deleted (unless committed or a volume is used).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Working with Images — Command Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  docker pull — Downloading Images
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose:&lt;/strong&gt; Download images from a registry to your local machine. Pull without &lt;code&gt;docker run&lt;/code&gt; when you want the image without starting a container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic pull — pulls latest tag (avoid in production!)&lt;/span&gt;
docker pull nginx

&lt;span class="c"&gt;# Specific tag pull (recommended)&lt;/span&gt;
docker pull nginx:1.25.3-alpine

&lt;span class="c"&gt;# Specific digest pull (MOST RECOMMENDED for production)&lt;/span&gt;
docker pull nginx@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c

&lt;span class="c"&gt;# Pull from private registry&lt;/span&gt;
docker pull myregistry.company.com/myapp:v1.2.3

&lt;span class="c"&gt;# Pull from AWS ECR (login first)&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1 | &lt;span class="se"&gt;\&lt;/span&gt;
  docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  123456789012.dkr.ecr.ap-south-1.amazonaws.com

docker pull 123456789012.dkr.ecr.ap-south-1.amazonaws.com/myapp:latest

&lt;span class="c"&gt;# Pull from GCR&lt;/span&gt;
docker pull gcr.io/my-project/myapp:v2.0.0

&lt;span class="c"&gt;# Pull from GitHub Container Registry&lt;/span&gt;
docker pull ghcr.io/myorg/myapp:main

&lt;span class="c"&gt;# Specify platform (ARM/AMD64)&lt;/span&gt;
docker pull &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64 nginx:latest
docker pull &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/arm64 nginx:latest  &lt;span class="c"&gt;# For Apple Silicon&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Always specify a tag when pulling. &lt;code&gt;docker pull nginx&lt;/code&gt; = &lt;code&gt;docker pull nginx:latest&lt;/code&gt;. If the &lt;code&gt;latest&lt;/code&gt; tag is updated tomorrow, your CI/CD will pull a different image. Fix: &lt;code&gt;docker pull nginx:1.25.3&lt;/code&gt; or use a digest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker images — Listing Images
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic list&lt;/span&gt;
docker images
docker image &lt;span class="nb"&gt;ls&lt;/span&gt;  &lt;span class="c"&gt;# Same command, new syntax&lt;/span&gt;

&lt;span class="c"&gt;# Table format with size&lt;/span&gt;
docker images &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.Repository}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Tag}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Size}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.CreatedAt}}"&lt;/span&gt;

&lt;span class="c"&gt;# Filter dangling images (untagged, build artifacts)&lt;/span&gt;
docker images &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;dangling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Filter by label&lt;/span&gt;
docker images &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="c"&gt;# Image IDs only (useful for scripting)&lt;/span&gt;
docker images &lt;span class="nt"&gt;-q&lt;/span&gt;

&lt;span class="c"&gt;# Show images for a specific repository&lt;/span&gt;
docker images nginx

&lt;span class="c"&gt;# Custom format&lt;/span&gt;
docker images &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"{{.ID}}: {{.Repository}}:{{.Tag}} — {{.Size}}"&lt;/span&gt;

&lt;span class="c"&gt;# All images (including intermediates)&lt;/span&gt;
docker images &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;# Images with digests&lt;/span&gt;
docker images &lt;span class="nt"&gt;--digests&lt;/span&gt;

&lt;span class="c"&gt;# Sort by size&lt;/span&gt;
docker images &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"{{.Size}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Repository}}:{{.Tag}}"&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker rmi — Deleting Images
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove by name&lt;/span&gt;
docker rmi nginx
docker image &lt;span class="nb"&gt;rm &lt;/span&gt;nginx

&lt;span class="c"&gt;# Specify tag (when multiple tags exist)&lt;/span&gt;
docker rmi nginx:1.25.3-alpine

&lt;span class="c"&gt;# Remove by image ID&lt;/span&gt;
docker rmi e784f4560448

&lt;span class="c"&gt;# Remove multiple images at once&lt;/span&gt;
docker rmi nginx:latest ubuntu:20.04 redis:alpine

&lt;span class="c"&gt;# Force remove — even if a container is using it (dangerous!)&lt;/span&gt;
docker rmi &lt;span class="nt"&gt;-f&lt;/span&gt; nginx:latest

&lt;span class="c"&gt;# Remove dangling images (safe cleanup)&lt;/span&gt;
docker image prune

&lt;span class="c"&gt;# Remove all unused images (aggressive cleanup)&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;# Remove without confirmation&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-f&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Nuclear option — delete all images (careful!)&lt;/span&gt;
docker rmi &lt;span class="si"&gt;$(&lt;/span&gt;docker images &lt;span class="nt"&gt;-q&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Delete by age filter&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"until=24h"&lt;/span&gt;   &lt;span class="c"&gt;# Older than 24 hours&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"until=168h"&lt;/span&gt;  &lt;span class="c"&gt;# Older than 7 days&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; An image used by a running container cannot be deleted — &lt;code&gt;docker rmi&lt;/code&gt; will fail. Stop and remove the container first: &lt;code&gt;docker stop &amp;lt;container&amp;gt; &amp;amp;&amp;amp; docker rm &amp;lt;container&amp;gt;&lt;/code&gt;, then &lt;code&gt;docker rmi &amp;lt;image&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker tag — Tagging Images
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Tag for Docker Hub&lt;/span&gt;
docker tag myapp:latest myusername/myapp:v1.0.0
docker tag myapp:latest myusername/myapp:latest

&lt;span class="c"&gt;# Tag for AWS ECR&lt;/span&gt;
docker tag myapp:latest &lt;span class="se"&gt;\&lt;/span&gt;
  123456789012.dkr.ecr.ap-south-1.amazonaws.com/myapp:v1.0.0

&lt;span class="c"&gt;# Tag for GCR&lt;/span&gt;
docker tag myapp:latest gcr.io/my-gcp-project/myapp:v1.0.0

&lt;span class="c"&gt;# Tag for GitHub Container Registry&lt;/span&gt;
docker tag myapp:latest ghcr.io/myorg/myapp:v1.0.0
docker tag myapp:latest ghcr.io/myorg/myapp:main-abc1234  &lt;span class="c"&gt;# Git commit hash&lt;/span&gt;

&lt;span class="c"&gt;# CI/CD common pattern (git hash + version)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_HASH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1.2.3"&lt;/span&gt;
docker tag myapp:latest myregistry.io/myapp:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GIT_HASH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
docker tag myapp:latest myregistry.io/myapp:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
docker tag myapp:latest myregistry.io/myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip (Tagging Strategy for CI/CD):&lt;/strong&gt; Good tagging strategy: (1) Git commit hash tag — always, immutable and traceable. (2) Semantic version tag. (3) Branch name tag (e.g., &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt;). (4) &lt;code&gt;latest&lt;/code&gt; only on main/master branch. This strategy makes both rollback and debugging easy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker save &amp;amp; docker load — Transferring Images
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Save a single image&lt;/span&gt;
docker save &lt;span class="nt"&gt;-o&lt;/span&gt; myapp.tar myapp:latest

&lt;span class="c"&gt;# Save multiple images into one tar&lt;/span&gt;
docker save &lt;span class="nt"&gt;-o&lt;/span&gt; all-images.tar nginx:latest redis:alpine postgres:15

&lt;span class="c"&gt;# Compressed save (saves disk space)&lt;/span&gt;
docker save myapp:latest | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; myapp.tar.gz

&lt;span class="c"&gt;# Load from tar file&lt;/span&gt;
docker load &lt;span class="nt"&gt;-i&lt;/span&gt; myapp.tar

&lt;span class="c"&gt;# Load compressed&lt;/span&gt;
&lt;span class="nb"&gt;gunzip&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; myapp.tar.gz | docker load

&lt;span class="c"&gt;# Transfer via SSH (no file on disk!)&lt;/span&gt;
docker save myapp:latest | ssh user@remote-server docker load

&lt;span class="c"&gt;# Verify loaded image&lt;/span&gt;
docker images | &lt;span class="nb"&gt;grep &lt;/span&gt;myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker history — Viewing Image Layers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View image history&lt;/span&gt;
docker &lt;span class="nb"&gt;history &lt;/span&gt;nginx

&lt;span class="c"&gt;# Full commands (not truncated)&lt;/span&gt;
docker &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--no-trunc&lt;/span&gt; nginx

&lt;span class="c"&gt;# Custom format&lt;/span&gt;
docker &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.ID}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Size}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.CreatedBy}}"&lt;/span&gt; nginx

&lt;span class="c"&gt;# Identify large layers&lt;/span&gt;
docker &lt;span class="nb"&gt;history &lt;/span&gt;nginx &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"{{.Size}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.CreatedBy}}"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"^0B"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rh&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker inspect — Deep Inspection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full JSON inspection&lt;/span&gt;
docker inspect nginx
docker inspect nginx | jq  &lt;span class="c"&gt;# Pretty printed (install jq: sudo apt install jq)&lt;/span&gt;

&lt;span class="c"&gt;# Extract specific fields (Go template syntax)&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Config.Env}}'&lt;/span&gt; nginx
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Config.ExposedPorts}}'&lt;/span&gt; nginx
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Config.Cmd}}'&lt;/span&gt; nginx

&lt;span class="c"&gt;# For containers: get IP address&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.NetworkSettings.IPAddress}}'&lt;/span&gt; my_container

&lt;span class="c"&gt;# Container mount points&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .Mounts}}'&lt;/span&gt; my_container | jq

&lt;span class="c"&gt;# Container restart policy&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.HostConfig.RestartPolicy}}'&lt;/span&gt; my_container

&lt;span class="c"&gt;# Image layers (layer hashes)&lt;/span&gt;
docker inspect nginx | jq &lt;span class="s1"&gt;'.[0].RootFS.Layers'&lt;/span&gt;

&lt;span class="c"&gt;# Container environment variables&lt;/span&gt;
docker inspect my_container | jq &lt;span class="s1"&gt;'.[0].Config.Env[]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker push — Uploading to Registry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Login first&lt;/span&gt;
docker login                               &lt;span class="c"&gt;# Docker Hub&lt;/span&gt;
docker login myregistry.company.com        &lt;span class="c"&gt;# Private registry&lt;/span&gt;

&lt;span class="c"&gt;# AWS ECR login&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1 | &lt;span class="se"&gt;\&lt;/span&gt;
  docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  123456789012.dkr.ecr.ap-south-1.amazonaws.com

&lt;span class="c"&gt;# GitHub Container Registry login&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt; | docker login ghcr.io &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$GITHUB_USERNAME&lt;/span&gt; &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;

&lt;span class="c"&gt;# Push&lt;/span&gt;
docker push myusername/myapp:v1.0.0
docker push myusername/myapp:latest

&lt;span class="c"&gt;# Push multiple tags (loop)&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;TAG &lt;span class="k"&gt;in &lt;/span&gt;v1.0.0 v1.0 v1 latest&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;docker push myregistry.io/myapp:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Logout (for security on shared machines)&lt;/span&gt;
docker &lt;span class="nb"&gt;logout
&lt;/span&gt;docker &lt;span class="nb"&gt;logout &lt;/span&gt;myregistry.company.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Image Layers Deep Dive — Caching &amp;amp; Optimization
&lt;/h2&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;
Place stable layers (base image, dependencies) below volatile ones (source code) so Docker's layer cache is reused on every incremental build.





&lt;h3&gt;
  
  
  OverlayFS — Technical Deep Dive
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OverlayFS Component&lt;/th&gt;
&lt;th&gt;Docker Equivalent&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lowerdir&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Image layers (read-only)&lt;/td&gt;
&lt;td&gt;Base image + all intermediate layers — never modified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upperdir&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Container's writable layer&lt;/td&gt;
&lt;td&gt;Container-specific changes go here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workdir&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internal OverlayFS use&lt;/td&gt;
&lt;td&gt;Staging area for atomic operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Container's filesystem view&lt;/td&gt;
&lt;td&gt;Unified mount — what the container sees&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View OverlayFS mount for a running container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; mynginx nginx

&lt;span class="c"&gt;# Get mount info&lt;/span&gt;
docker inspect mynginx | jq &lt;span class="s1"&gt;'.[0].GraphDriver'&lt;/span&gt;
&lt;span class="c"&gt;# {&lt;/span&gt;
&lt;span class="c"&gt;#   "Data": {&lt;/span&gt;
&lt;span class="c"&gt;#     "LowerDir": "/var/lib/docker/overlay2/abc.../diff:/var/lib/docker/overlay2/def.../diff",&lt;/span&gt;
&lt;span class="c"&gt;#     "MergedDir": "/var/lib/docker/overlay2/xyz.../merged",&lt;/span&gt;
&lt;span class="c"&gt;#     "UpperDir": "/var/lib/docker/overlay2/xyz.../diff",&lt;/span&gt;
&lt;span class="c"&gt;#     "WorkDir": "/var/lib/docker/overlay2/xyz.../work"&lt;/span&gt;
&lt;span class="c"&gt;#   },&lt;/span&gt;
&lt;span class="c"&gt;#   "Name": "overlay2"&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;

&lt;span class="c"&gt;# View actual filesystem layering on host&lt;/span&gt;
&lt;span class="nb"&gt;sudo ls&lt;/span&gt; /var/lib/docker/overlay2/

&lt;span class="c"&gt;# View mount point on host&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/mounts | &lt;span class="nb"&gt;grep &lt;/span&gt;overlay

&lt;span class="c"&gt;# Demonstrate Copy-on-Write&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;mynginx sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"echo 'hello' &amp;gt; /usr/share/nginx/html/test.html"&lt;/span&gt;
&lt;span class="c"&gt;# The file went into the container's upperdir, NOT into the image.&lt;/span&gt;
&lt;span class="c"&gt;# The image remains unchanged.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Layer Caching — The Secret of Build Speed
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# First build — everything downloaded and executed (slow)&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:v1 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# Step 1/5 : FROM node:18-alpine      → DOWNLOADING (slow)&lt;/span&gt;
&lt;span class="c"&gt;# Step 2/5 : WORKDIR /app             → Running&lt;/span&gt;
&lt;span class="c"&gt;# Step 3/5 : COPY package*.json ./    → Running&lt;/span&gt;
&lt;span class="c"&gt;# Step 4/5 : RUN npm install          → Installing (slow!)&lt;/span&gt;
&lt;span class="c"&gt;# Step 5/5 : COPY . .                 → Running&lt;/span&gt;

&lt;span class="c"&gt;# Second build (only source code changed)&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:v2 &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# Step 1/5 : FROM node:18-alpine      → Using cache  ✓ (instant!)&lt;/span&gt;
&lt;span class="c"&gt;# Step 2/5 : WORKDIR /app             → Using cache  ✓ (instant!)&lt;/span&gt;
&lt;span class="c"&gt;# Step 3/5 : COPY package*.json ./    → Using cache  ✓ (package.json unchanged)&lt;/span&gt;
&lt;span class="c"&gt;# Step 4/5 : RUN npm install          → Using cache  ✓ (instant!)&lt;/span&gt;
&lt;span class="c"&gt;# Step 5/5 : COPY . .                 → Running      ← Only this re-runs!&lt;/span&gt;
&lt;span class="c"&gt;# Total time: 2 seconds vs 3 minutes!&lt;/span&gt;

&lt;span class="c"&gt;# Force bypass cache&lt;/span&gt;
docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:fresh &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Use cache from external source (CI/CD optimization)&lt;/span&gt;
docker build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cache-from&lt;/span&gt; myregistry.io/myapp:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:new &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cache Invalidation — When Cache Breaks
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instruction&lt;/th&gt;
&lt;th&gt;Cache Invalidates When&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base image digest changes&lt;/td&gt;
&lt;td&gt;Rarely — unless you explicitly pull new base&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RUN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Command text changes&lt;/td&gt;
&lt;td&gt;Even whitespace change = cache miss!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COPY / ADD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;File content changes (checksum)&lt;/td&gt;
&lt;td&gt;Even 1 byte change = cache miss for this layer + all subsequent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ENV&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Value changes&lt;/td&gt;
&lt;td&gt;All subsequent layers also invalidated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ARG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build arg value changes&lt;/td&gt;
&lt;td&gt;Be careful with VERSION args — they invalidate builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WORKDIR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path changes&lt;/td&gt;
&lt;td&gt;Usually stable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; A cache miss in one layer causes ALL subsequent layers to re-run. This is why layer ordering is so important — put frequently changing instructions last, stable instructions first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Layer Ordering — Cache Optimization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ BAD — npm install runs EVERY TIME any file changes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .           # Source code AND package.json copied together&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;    &lt;span class="c"&gt;# Re-runs even if only App.jsx changed!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ GOOD — npm install only when package.json changes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Step 1: Copy ONLY package files first&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Install dependencies (CACHED as long as package.json is unchanged)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: Now copy source code (this layer changes with source changes)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Serve&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ BEST — Python/pip example follows the same principle&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Dependencies first (stable layer)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Source code later (frequently changing)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ BEST — Java/Maven example&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3.9-eclipse-temurin-17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy pom.xml first (dependencies are stable)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;mvn dependency:go-offline &lt;span class="nt"&gt;-q&lt;/span&gt;  &lt;span class="c"&gt;# Download all dependencies (cached!)&lt;/span&gt;

&lt;span class="c"&gt;# Source code later&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;mvn package &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-DskipTests&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eclipse-temurin:17-jre-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/target/*.jar app.jar&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (Golden Rule of Layer Ordering):&lt;/strong&gt; Think of it this way: "What changes least goes first." Order of stability: Base image &amp;gt; System packages &amp;gt; Dependency files &amp;gt; Dependencies install &amp;gt; Config files &amp;gt; Source code &amp;gt; Build artifacts. Following this order will dramatically speed up CI/CD builds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Layer Sharing — Memory &amp;amp; Disk Efficiency
&lt;/h3&gt;

&lt;p&gt;When 50 Node.js microservices all use the same &lt;code&gt;node:18-alpine&lt;/code&gt; base image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Without sharing: 50 × 120MB = 6 GB&lt;/li&gt;
&lt;li&gt;With Docker layer sharing: 120MB (base, stored once) + 50 × app_size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means more containers fit per Kubernetes node, node startup is faster (layer already cached), and network bandwidth is saved during pulls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer Analysis Tools
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Method 1: docker history (built-in)&lt;/span&gt;
docker &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--no-trunc&lt;/span&gt; myapp:latest

&lt;span class="c"&gt;# Method 2: docker inspect layers&lt;/span&gt;
docker inspect myapp:latest | jq &lt;span class="s1"&gt;'.[0].RootFS.Layers'&lt;/span&gt;

&lt;span class="c"&gt;# Method 3: dive tool (BEST for optimization)&lt;/span&gt;
&lt;span class="c"&gt;# Installation&lt;/span&gt;
wget https://github.com/wagoodman/dive/releases/download/v0.12.0/dive_0.12.0_linux_amd64.deb
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; ./dive_0.12.0_linux_amd64.deb

&lt;span class="c"&gt;# Interactive layer analysis&lt;/span&gt;
dive myapp:latest
&lt;span class="c"&gt;# Left panel: layer list + size&lt;/span&gt;
&lt;span class="c"&gt;# Right panel: filesystem changes per layer&lt;/span&gt;
&lt;span class="c"&gt;# Shows wasted space — e.g., files deleted in later layers&lt;/span&gt;
&lt;span class="c"&gt;# Gives an efficiency score&lt;/span&gt;

&lt;span class="c"&gt;# CI mode (automated check)&lt;/span&gt;
dive myapp:latest &lt;span class="nt"&gt;--ci&lt;/span&gt;   &lt;span class="c"&gt;# Fails if efficiency is below threshold&lt;/span&gt;

&lt;span class="c"&gt;# Method 4: BuildKit inline --progress=plain&lt;/span&gt;
&lt;span class="nv"&gt;DOCKER_BUILDKIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 docker build &lt;span class="nt"&gt;--progress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;plain &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:latest &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# Shows each step's timing and cache status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced Layer Optimization Techniques
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Technique 1: Combine RUN commands (fewer layers)&lt;/span&gt;
&lt;span class="c"&gt;# ❌ BAD — 3 layers for apt operations&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl wget
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# ✅ GOOD — Single layer, cleanup in SAME layer&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl wget &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# IMPORTANT: Cleanup must be in the SAME RUN instruction!&lt;/span&gt;
&lt;span class="c"&gt;# Cleaning in a separate RUN does NOT reduce layer size&lt;/span&gt;
&lt;span class="c"&gt;# (the layer with apt cache already exists)&lt;/span&gt;

&lt;span class="c"&gt;# Technique 2: Multi-stage builds (drastically reduce SIZE)&lt;/span&gt;
&lt;span class="c"&gt;# Build stage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--production&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;  &lt;span class="c"&gt;# All deps including devDependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build               &lt;span class="c"&gt;# Create production build&lt;/span&gt;

&lt;span class="c"&gt;# Production stage (runtime only!)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine             # Slim base (50MB vs 1GB!)&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="c"&gt;# Build tools, devDeps, source code do not go into the production image!&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;

&lt;span class="c"&gt;# Result: 1.2GB → 180MB image!&lt;/span&gt;

&lt;span class="c"&gt;# Technique 3: .dockerignore (like .gitignore for Docker)&lt;/span&gt;
&lt;span class="c"&gt;# node_modules/      ← exclude from COPY&lt;/span&gt;
&lt;span class="c"&gt;# .git/              ← unnecessary&lt;/span&gt;
&lt;span class="c"&gt;# dist/              ← build output (will be rebuilt)&lt;/span&gt;
&lt;span class="c"&gt;# *.log              ← log files&lt;/span&gt;
&lt;span class="c"&gt;# .env               ← Secrets! NEVER in image&lt;/span&gt;
&lt;span class="c"&gt;# coverage/          ← test coverage reports&lt;/span&gt;

&lt;span class="c"&gt;# Technique 4: Specific COPY (avoid COPY . .)&lt;/span&gt;
&lt;span class="c"&gt;# ❌ BAD&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .              # Copies everything including .git, tests, docs&lt;/span&gt;

&lt;span class="c"&gt;# ✅ GOOD&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ ./src/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; public/ ./public/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip (Senior Interview Question):&lt;/strong&gt; &lt;strong&gt;Q: "A Docker image has grown to 2GB — how would you optimize it?"&lt;/strong&gt; &lt;strong&gt;A:&lt;/strong&gt; (1) Use &lt;code&gt;docker history --no-trunc myimage&lt;/code&gt; to identify large layers. (2) Use multi-stage builds — don't include build tools in production. (3) Use Alpine base image (50MB vs 900MB). (4) Run &lt;code&gt;apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;/code&gt; in the same RUN instruction. (5) Use &lt;code&gt;.dockerignore&lt;/code&gt; to exclude unnecessary files. (6) Use the &lt;code&gt;dive&lt;/code&gt; tool to find wasted space. (7) Combine RUN instructions. Typically, 2GB → 150-200MB is achievable!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Container Lifecycle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Container States
&lt;/h3&gt;

&lt;p&gt;Every Docker container passes through multiple states during its life. Think of them like React component lifecycle (mount, update, unmount) — but more complex:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CREATED&lt;/strong&gt; — Container created via &lt;code&gt;docker create&lt;/code&gt; but not started&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RUNNING&lt;/strong&gt; — Container is active&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PAUSED&lt;/strong&gt; — Frozen, consuming no CPU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EXITED&lt;/strong&gt; — Stopped or done (via &lt;code&gt;docker stop&lt;/code&gt;, &lt;code&gt;docker kill&lt;/code&gt;, exit, or crash)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RESTARTING&lt;/strong&gt; — Auto-restart loop in progress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEAD&lt;/strong&gt; — Restart attempts failed, max retries exceeded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REMOVED&lt;/strong&gt; — Container permanently deleted via &lt;code&gt;docker rm&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Image vs Container
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Image&lt;/th&gt;
&lt;th&gt;Container&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nature&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blueprint / Template&lt;/td&gt;
&lt;td&gt;Running instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Immutable, static (never changes)&lt;/td&gt;
&lt;td&gt;Mutable, stateful (can change)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Read-only layers&lt;/td&gt;
&lt;td&gt;Image layers + writable layer on top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multiple instances&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shared by all containers using it&lt;/td&gt;
&lt;td&gt;Each container fully independent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lifecycle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build once, use forever&lt;/td&gt;
&lt;td&gt;Create → Run → Stop → Delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disk space&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stored once even if 100 containers use it&lt;/td&gt;
&lt;td&gt;Only writable layer extra per container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Analogy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Recipe&lt;/td&gt;
&lt;td&gt;The actual dish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;React analogy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React Component class/function&lt;/td&gt;
&lt;td&gt;Component instance rendered in DOM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Container Lifecycle Commands — Quick Reference
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full lifecycle — a container's complete journey&lt;/span&gt;
docker create &lt;span class="nt"&gt;--name&lt;/span&gt; myapp nginx          &lt;span class="c"&gt;# Create but don't start&lt;/span&gt;
docker start myapp                        &lt;span class="c"&gt;# Start it&lt;/span&gt;
docker pause myapp                        &lt;span class="c"&gt;# Freeze it (stops CPU usage)&lt;/span&gt;
docker unpause myapp                      &lt;span class="c"&gt;# Unfreeze it&lt;/span&gt;
docker stop myapp                         &lt;span class="c"&gt;# Graceful stop (SIGTERM then SIGKILL)&lt;/span&gt;
docker start myapp                        &lt;span class="c"&gt;# Start again&lt;/span&gt;
docker restart myapp                      &lt;span class="c"&gt;# Stop + Start in one command&lt;/span&gt;
docker &lt;span class="nb"&gt;kill &lt;/span&gt;myapp                         &lt;span class="c"&gt;# Immediate kill (SIGKILL)&lt;/span&gt;
docker &lt;span class="nb"&gt;rm &lt;/span&gt;myapp                           &lt;span class="c"&gt;# Remove container (permanently delete)&lt;/span&gt;

&lt;span class="c"&gt;# One-liner (create + start + remove when done — for temporary tasks)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; temp-app nginx

&lt;span class="c"&gt;# Check current state of all containers&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;docker run&lt;/code&gt; = &lt;code&gt;docker create&lt;/code&gt; + &lt;code&gt;docker start&lt;/code&gt; + &lt;code&gt;docker attach&lt;/code&gt; (in foreground mode). CI/CD pipelines almost always use &lt;code&gt;docker run&lt;/code&gt;. &lt;code&gt;docker create&lt;/code&gt; is useful when you want to set up a container first and start it later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; If a container enters the DEAD state, it cannot be restarted. You can only remove it with &lt;code&gt;docker rm&lt;/code&gt;. This happens when restart policy max retries are exhausted. Monitor DEAD containers in production!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Essential Container Commands
&lt;/h2&gt;

&lt;h3&gt;
  
  
  docker run — The Master Command
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;docker run&lt;/code&gt; creates a container, starts it, and optionally attaches to it. Understanding its options is essential.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full syntax&lt;/span&gt;
docker run &lt;span class="o"&gt;[&lt;/span&gt;OPTIONS] IMAGE &lt;span class="o"&gt;[&lt;/span&gt;COMMAND] &lt;span class="o"&gt;[&lt;/span&gt;ARG...]

&lt;span class="c"&gt;# -d: Detached mode (run in background)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; nginx
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; my-nginx nginx

&lt;span class="c"&gt;# -p: Port mapping (host_port:container_port)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx             &lt;span class="c"&gt;# Host 8080 → Container 80&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:8080:80 nginx  &lt;span class="c"&gt;# Bind to specific IP&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;-p&lt;/span&gt; 443:443 nginx &lt;span class="c"&gt;# Multiple ports&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; nginx                     &lt;span class="c"&gt;# Auto-map ALL EXPOSED ports&lt;/span&gt;

&lt;span class="c"&gt;# -v: Volume mount (old syntax)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; /host/path:/container/path nginx
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; myvolume:/app/data nginx
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app node:18       &lt;span class="c"&gt;# Mount current directory&lt;/span&gt;

&lt;span class="c"&gt;# --mount: Volume mount (new, preferred syntax)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;/host/path,target&lt;span class="o"&gt;=&lt;/span&gt;/container/path nginx
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;volume,source&lt;span class="o"&gt;=&lt;/span&gt;myvolume,target&lt;span class="o"&gt;=&lt;/span&gt;/data nginx
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tmpfs,target&lt;span class="o"&gt;=&lt;/span&gt;/tmp nginx

&lt;span class="c"&gt;# -e: Environment variables&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production node:18
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DB_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5432 myapp
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env myapp        &lt;span class="c"&gt;# Load from .env file&lt;/span&gt;

&lt;span class="c"&gt;# --name: Give the container a name&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; my-api myapp:v1.0

&lt;span class="c"&gt;# --rm: Automatically remove when it exits&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; ubuntu &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;        &lt;span class="c"&gt;# Perfect for one-off commands&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app node:18 npm &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# --network: Connect to a network&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; mynetwork nginx
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; host nginx         &lt;span class="c"&gt;# Use host machine's network&lt;/span&gt;

&lt;span class="c"&gt;# --restart: Restart policy&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt; no nginx              &lt;span class="c"&gt;# Never restart (default)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt; always nginx          &lt;span class="c"&gt;# Always restart&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt; on-failure nginx      &lt;span class="c"&gt;# Restart only on failure&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt; on-failure:3 nginx    &lt;span class="c"&gt;# Max 3 retries&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped nginx  &lt;span class="c"&gt;# Restart unless manually stopped&lt;/span&gt;

&lt;span class="c"&gt;# -it: Interactive Terminal (enter the container)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; ubuntu bash               &lt;span class="c"&gt;# Start bash session&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; ubuntu bash          &lt;span class="c"&gt;# Session ends → container deleted&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; node:18 node              &lt;span class="c"&gt;# Node.js REPL&lt;/span&gt;

&lt;span class="c"&gt;# Resource Limits (CRITICAL IN PRODUCTION!)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m nginx              &lt;span class="c"&gt;# Max 512MB RAM&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="nt"&gt;--memory-swap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1g nginx  &lt;span class="c"&gt;# RAM + swap limit&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 nginx                 &lt;span class="c"&gt;# 50% of one CPU&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 nginx                   &lt;span class="c"&gt;# 2 full CPUs&lt;/span&gt;

&lt;span class="c"&gt;# --user: Run as a specific user (security)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; 1000:1000 nginx
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; nobody nginx

&lt;span class="c"&gt;# Real-world Production Example — all together&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; my-react-app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.myapp.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /var/log/app:/app/logs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;256m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; app-network &lt;span class="se"&gt;\&lt;/span&gt;
  myreactapp:v1.2.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (Production):&lt;/strong&gt; Without resource limits, a single buggy container can consume all server memory. A memory limit means the OOM killer will kill the container — not the entire server. CPU limits ensure fair sharing between containers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker ps — List Containers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Running containers only&lt;/span&gt;
docker ps

&lt;span class="c"&gt;# ALL containers (including stopped)&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;# IDs only (useful in scripts)&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-q&lt;/span&gt;

&lt;span class="c"&gt;# Last 5 created containers&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-n&lt;/span&gt; 5

&lt;span class="c"&gt;# Custom format&lt;/span&gt;
docker ps &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.ID}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Names}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Status}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Ports}}"&lt;/span&gt;
docker ps &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"{{.Names}}: {{.Status}}"&lt;/span&gt;

&lt;span class="c"&gt;# Filter by status&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;running
docker ps &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;exited
docker ps &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp
docker ps &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;ancestor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx       &lt;span class="c"&gt;# Containers using nginx image&lt;/span&gt;

&lt;span class="c"&gt;# Show container sizes&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker stop / start / restart
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Graceful stop — sends SIGTERM, waits 10 sec, then SIGKILL&lt;/span&gt;
docker stop myapp
docker stop &lt;span class="nt"&gt;--time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30 myapp       &lt;span class="c"&gt;# Wait 30 sec before SIGKILL&lt;/span&gt;

&lt;span class="c"&gt;# Stop multiple containers at once&lt;/span&gt;
docker stop container1 container2 container3

&lt;span class="c"&gt;# Stop all running containers&lt;/span&gt;
docker stop &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Start a stopped container&lt;/span&gt;
docker start myapp
docker start &lt;span class="nt"&gt;-a&lt;/span&gt; myapp             &lt;span class="c"&gt;# Start + attach to output&lt;/span&gt;

&lt;span class="c"&gt;# Restart = stop + start&lt;/span&gt;
docker restart myapp
docker restart &lt;span class="nt"&gt;--time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 myapp     &lt;span class="c"&gt;# Wait 5 sec before SIGKILL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker pause / unpause — Freeze a Container
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Freeze container (uses cgroups freezer)&lt;/span&gt;
&lt;span class="c"&gt;# Container state is preserved, no CPU consumed&lt;/span&gt;
docker pause myapp

&lt;span class="c"&gt;# Unfreeze — container continues from where it left off&lt;/span&gt;
docker unpause myapp

&lt;span class="c"&gt;# When to use:&lt;/span&gt;
&lt;span class="c"&gt;# - Load testing: pause one container, test another&lt;/span&gt;
&lt;span class="c"&gt;# - Debug: capture exact state without losing data&lt;/span&gt;
&lt;span class="c"&gt;# - Resource saving: temporarily free CPU&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;docker pause&lt;/code&gt; = process freeze (still in memory, state preserved, no CPU). &lt;code&gt;docker stop&lt;/code&gt; = process terminate (SIGTERM → SIGKILL). Unpause is very fast because the process doesn't restart — it just unfreezes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker kill — Immediate Termination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Immediate kill (SIGKILL — no grace period)&lt;/span&gt;
docker &lt;span class="nb"&gt;kill &lt;/span&gt;myapp

&lt;span class="c"&gt;# Send a specific signal&lt;/span&gt;
docker &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;--signal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SIGTERM myapp    &lt;span class="c"&gt;# Graceful shutdown request&lt;/span&gt;
docker &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;--signal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SIGHUP myapp     &lt;span class="c"&gt;# Nginx config reload (without restart!)&lt;/span&gt;
docker &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;--signal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9 myapp          &lt;span class="c"&gt;# SIGKILL (same as default kill)&lt;/span&gt;
docker &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;--signal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SIGUSR1 myapp    &lt;span class="c"&gt;# App-specific custom signal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker rm — Remove Containers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove a stopped container&lt;/span&gt;
docker &lt;span class="nb"&gt;rm &lt;/span&gt;myapp

&lt;span class="c"&gt;# Force remove a running container (think before you do this!)&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; myapp

&lt;span class="c"&gt;# Remove container and its anonymous volume&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; myapp

&lt;span class="c"&gt;# Remove all stopped containers at once (recommended!)&lt;/span&gt;
docker container prune
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-aq&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;exited&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Remove ALL containers (running + stopped) — nuclear option!&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-aq&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker logs — View Logs (First Debug Tool)
&lt;/h3&gt;

&lt;p&gt;When something is not working, look at logs first!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic logs&lt;/span&gt;
docker logs myapp

&lt;span class="c"&gt;# Follow (like tail -f, real-time updates)&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; myapp

&lt;span class="c"&gt;# Last 100 lines only&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 100 myapp

&lt;span class="c"&gt;# Show with timestamps&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-t&lt;/span&gt; myapp

&lt;span class="c"&gt;# Logs since a specific time&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; 2024-01-01 myapp
docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; 1h myapp              &lt;span class="c"&gt;# Last 1 hour of logs&lt;/span&gt;

&lt;span class="c"&gt;# Most useful combination — last 50 lines follow with timestamps&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;--tail&lt;/span&gt; 50 myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;docker logs&lt;/code&gt; works when the application writes to stdout/stderr. If the app writes to a file (e.g., &lt;code&gt;/var/log/app.log&lt;/code&gt;), use &lt;code&gt;docker exec -it myapp tail -f /var/log/app.log&lt;/code&gt;, or mount a volume and read from the host.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker exec — Enter a Running Container
&lt;/h3&gt;

&lt;p&gt;This command is used daily for debugging. Enter a running container and investigate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run a command inside the container&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp &lt;span class="nb"&gt;ls&lt;/span&gt; /app
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/nginx/nginx.conf

&lt;span class="c"&gt;# Interactive bash session (most common use case)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp bash

&lt;span class="c"&gt;# Alpine-based containers: use sh (bash not available)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp sh

&lt;span class="c"&gt;# Login as root (when full access is needed)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; root myapp bash

&lt;span class="c"&gt;# With environment variable&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;myapp node debug.js

&lt;span class="c"&gt;# Real-world examples&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; postgres psql &lt;span class="nt"&gt;-U&lt;/span&gt; admin &lt;span class="nt"&gt;-d&lt;/span&gt; mydb    &lt;span class="c"&gt;# Enter PostgreSQL&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; redis redis-cli                   &lt;span class="c"&gt;# Open Redis CLI&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; nginx nginx &lt;span class="nt"&gt;-t&lt;/span&gt;                    &lt;span class="c"&gt;# Test nginx config&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; nginx nginx &lt;span class="nt"&gt;-s&lt;/span&gt; reload             &lt;span class="c"&gt;# Gracefully reload nginx&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp &lt;span class="nb"&gt;cat&lt;/span&gt; /app/config.json        &lt;span class="c"&gt;# Read config file&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp &lt;span class="nb"&gt;env&lt;/span&gt;                         &lt;span class="c"&gt;# View all env variables&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp ps aux                      &lt;span class="c"&gt;# View running processes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;code&gt;docker exec&lt;/code&gt; starts a new process inside the container. &lt;code&gt;docker attach&lt;/code&gt; connects to the container's main process — pressing Ctrl+C will STOP the container! For debugging, always use &lt;code&gt;exec&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker inspect — Extract Full Details
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full JSON info&lt;/span&gt;
docker inspect myapp

&lt;span class="c"&gt;# Extract specific info with Go templates&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.NetworkSettings.IPAddress}}'&lt;/span&gt; myapp
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Config.Env}}'&lt;/span&gt; myapp
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Config.Image}}'&lt;/span&gt; myapp
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.StartedAt}}'&lt;/span&gt; myapp

&lt;span class="c"&gt;# Port mappings&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .NetworkSettings.Ports}}'&lt;/span&gt; myapp | jq

&lt;span class="c"&gt;# Volume/mount info&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .Mounts}}'&lt;/span&gt; myapp | jq

&lt;span class="c"&gt;# Container exit code (for debugging)&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.ExitCode}}'&lt;/span&gt; myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker top, docker stats, docker cp, docker commit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View processes inside container (from host, without entering container)&lt;/span&gt;
docker top myapp
docker top myapp aux
docker top myapp &lt;span class="nt"&gt;-ef&lt;/span&gt;

&lt;span class="c"&gt;# Real-time resource usage (live dashboard)&lt;/span&gt;
docker stats
docker stats myapp
docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt;          &lt;span class="c"&gt;# One snapshot&lt;/span&gt;
docker stats &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.Name}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.CPUPerc}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.MemUsage}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.NetIO}}"&lt;/span&gt;

&lt;span class="c"&gt;# Copy files between container and host&lt;/span&gt;
docker &lt;span class="nb"&gt;cp &lt;/span&gt;myapp:/app/config.json ./config.json     &lt;span class="c"&gt;# Container → host&lt;/span&gt;
docker &lt;span class="nb"&gt;cp&lt;/span&gt; ./config.json myapp:/app/config.json     &lt;span class="c"&gt;# Host → container&lt;/span&gt;
docker &lt;span class="nb"&gt;cp &lt;/span&gt;myapp:/var/log/app.log ./app.log
&lt;span class="c"&gt;# Works with stopped containers too!&lt;/span&gt;

&lt;span class="c"&gt;# Create a new image from a modified container&lt;/span&gt;
docker commit myapp myapp-modified:v2
docker commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Added custom nginx config"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"Author"&lt;/span&gt; myapp myapp:custom
&lt;span class="c"&gt;# NOTE: Use Dockerfiles in production — docker commit is not reproducible!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Container Operations — Advanced Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Foreground vs Background — Attached vs Detached Mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Foreground (attached) — output visible, Ctrl+C stops the container&lt;/span&gt;
docker run nginx
&lt;span class="c"&gt;# Terminal is blocked, nginx logs appear here&lt;/span&gt;

&lt;span class="c"&gt;# Background (detached) — runs in background, terminal is free&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; nginx
&lt;span class="c"&gt;# Returns container ID, you can do other work&lt;/span&gt;

&lt;span class="c"&gt;# Attach to a running container's output&lt;/span&gt;
docker attach myapp
&lt;span class="c"&gt;# WARNING: Ctrl+C stops the container!&lt;/span&gt;
&lt;span class="c"&gt;# To detach without stopping: Ctrl+P then Ctrl+Q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Detaching Without Stopping — Critical Shortcut
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start an interactive session&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; ubuntu bash

&lt;span class="c"&gt;# To leave WITHOUT stopping the container:&lt;/span&gt;
&lt;span class="c"&gt;# Press Ctrl+P then Ctrl+Q (both quickly)&lt;/span&gt;
&lt;span class="c"&gt;# Container keeps running, you get your terminal back&lt;/span&gt;

&lt;span class="c"&gt;# Return later&lt;/span&gt;
docker attach ubuntu_container_name

&lt;span class="c"&gt;# exec vs attach — which to use?&lt;/span&gt;
&lt;span class="c"&gt;# exec — starts a new process (SAFE, always use this)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp bash

&lt;span class="c"&gt;# attach — connects to main process (RISKY for debugging)&lt;/span&gt;
docker attach myapp   &lt;span class="c"&gt;# Ctrl+C = container STOP!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; New users often use &lt;code&gt;docker attach&lt;/code&gt; and then press Ctrl+C thinking they are only detaching from the terminal — but the container STOPS! In production, always use &lt;code&gt;docker exec -it myapp bash&lt;/code&gt;. If you must use attach, detach with &lt;code&gt;Ctrl+P Ctrl+Q&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Container Exit Codes
&lt;/h3&gt;

&lt;p&gt;Exit codes tell you why a container stopped. Visible in &lt;code&gt;docker ps -a&lt;/code&gt; in the STATUS column as "Exited (137)".&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exit Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Common Cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;td&gt;Container completed its task successfully&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;General Error&lt;/td&gt;
&lt;td&gt;Application crash, unhandled exception, startup failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;125&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Docker daemon error&lt;/td&gt;
&lt;td&gt;Invalid &lt;code&gt;docker run&lt;/code&gt; options, Docker internal error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;126&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Command can't execute&lt;/td&gt;
&lt;td&gt;Permission error — command found but not executable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;127&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Command not found&lt;/td&gt;
&lt;td&gt;Wrong command or PATH issue, typo in CMD/ENTRYPOINT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;130&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ctrl+C (SIGINT)&lt;/td&gt;
&lt;td&gt;Manual interruption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;137&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SIGKILL (128+9)&lt;/td&gt;
&lt;td&gt;OOM killer or docker kill / docker stop timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;139&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Segmentation fault&lt;/td&gt;
&lt;td&gt;Application memory error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;143&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SIGTERM (128+15)&lt;/td&gt;
&lt;td&gt;docker stop — graceful shutdown signal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Signal-based exit codes = 128 + signal_number. SIGKILL = signal 9, exit code = 137. SIGTERM = signal 15, exit code = 143. Exit 137 almost always means OOM or force kill.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Restart Policies — Detailed
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# no (default) — Never restart&lt;/span&gt;
docker run &lt;span class="nt"&gt;--restart&lt;/span&gt; no nginx
&lt;span class="c"&gt;# Use case: one-off scripts, batch jobs, dev environment&lt;/span&gt;

&lt;span class="c"&gt;# always — Always restart (ignores exit code)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--restart&lt;/span&gt; always nginx
&lt;span class="c"&gt;# Note: Also auto-starts on Docker daemon restart!&lt;/span&gt;
&lt;span class="c"&gt;# Use case: critical services that must always be running&lt;/span&gt;

&lt;span class="c"&gt;# on-failure — Restart only on failure (non-zero exit)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--restart&lt;/span&gt; on-failure nginx         &lt;span class="c"&gt;# Unlimited retries&lt;/span&gt;
docker run &lt;span class="nt"&gt;--restart&lt;/span&gt; on-failure:5 nginx       &lt;span class="c"&gt;# Max 5 retries, then DEAD&lt;/span&gt;
&lt;span class="c"&gt;# Use case: services that may occasionally crash&lt;/span&gt;

&lt;span class="c"&gt;# unless-stopped — Like always, but respects manual stop&lt;/span&gt;
docker run &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped nginx
&lt;span class="c"&gt;# docker stop → will NOT restart on daemon restart&lt;/span&gt;
&lt;span class="c"&gt;# Use case: Production services (RECOMMENDED for most cases)&lt;/span&gt;

&lt;span class="c"&gt;# Change policy on a running container (no restart required)&lt;/span&gt;
docker update &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped myapp
docker update &lt;span class="nt"&gt;--restart&lt;/span&gt; no myapp              &lt;span class="c"&gt;# Disable restart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;Restart on crash?&lt;/th&gt;
&lt;th&gt;Restart on daemon restart?&lt;/th&gt;
&lt;th&gt;Restart after docker stop?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;always&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (even after docker stop!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;on-failure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes (non-zero exit only)&lt;/td&gt;
&lt;td&gt;Yes (if was running)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unless-stopped&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (respects manual stop)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Resource Monitoring
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Real-time stats dashboard (updates every 1 second)&lt;/span&gt;
watch &lt;span class="nt"&gt;-n&lt;/span&gt; 1 docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt;

&lt;span class="c"&gt;# Docker system overall usage&lt;/span&gt;
docker system &lt;span class="nb"&gt;df&lt;/span&gt;              &lt;span class="c"&gt;# Disk usage — images, containers, volumes, build cache&lt;/span&gt;
docker system events          &lt;span class="c"&gt;# Live event stream&lt;/span&gt;
docker system info            &lt;span class="c"&gt;# Complete Docker system info&lt;/span&gt;

&lt;span class="c"&gt;# Monitor events (powerful debugging tool)&lt;/span&gt;
docker events                              &lt;span class="c"&gt;# All events real-time&lt;/span&gt;
docker events &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s1"&gt;'type=container'&lt;/span&gt;   &lt;span class="c"&gt;# Container events only&lt;/span&gt;
docker events &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s1"&gt;'event=die'&lt;/span&gt;        &lt;span class="c"&gt;# Container deaths&lt;/span&gt;
docker events &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s1"&gt;'event=start'&lt;/span&gt;      &lt;span class="c"&gt;# Container starts&lt;/span&gt;
docker events &lt;span class="nt"&gt;--since&lt;/span&gt; 1h                  &lt;span class="c"&gt;# Events from last 1 hour&lt;/span&gt;

&lt;span class="c"&gt;# Container health check status&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.Health.Status}}'&lt;/span&gt; myapp
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .State.Health}}'&lt;/span&gt; myapp | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cleanup Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check disk usage first&lt;/span&gt;
docker system &lt;span class="nb"&gt;df&lt;/span&gt;
&lt;span class="c"&gt;# TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE&lt;/span&gt;
&lt;span class="c"&gt;# Images          25        3         12.5GB    10.2GB (82%)&lt;/span&gt;
&lt;span class="c"&gt;# Containers      8         2         150MB     130MB (86%)&lt;/span&gt;
&lt;span class="c"&gt;# Local Volumes   15        3         4.2GB     3.8GB (90%)&lt;/span&gt;
&lt;span class="c"&gt;# Build Cache     -         -         2.1GB     2.1GB&lt;/span&gt;

&lt;span class="c"&gt;# Individual cleanup (specific resources)&lt;/span&gt;
docker container prune                      &lt;span class="c"&gt;# Remove stopped containers&lt;/span&gt;
docker image prune                          &lt;span class="c"&gt;# Remove dangling images&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt;                       &lt;span class="c"&gt;# Remove all unused images&lt;/span&gt;
docker volume prune                         &lt;span class="c"&gt;# Remove unused volumes&lt;/span&gt;
docker network prune                        &lt;span class="c"&gt;# Remove unused networks&lt;/span&gt;

&lt;span class="c"&gt;# Nuclear options — use carefully!&lt;/span&gt;
docker system prune                         &lt;span class="c"&gt;# Remove everything unused (no volumes)&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--volumes&lt;/span&gt;            &lt;span class="c"&gt;# Remove everything including volumes&lt;/span&gt;

&lt;span class="c"&gt;# Non-interactive (for CI/CD scripts)&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-f&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="nt"&gt;--volumes&lt;/span&gt;           &lt;span class="c"&gt;# Full nuclear, no confirmation&lt;/span&gt;

&lt;span class="c"&gt;# Smart cleanup — only remove old items&lt;/span&gt;
docker container prune &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"until=24h"&lt;/span&gt;    &lt;span class="c"&gt;# Containers older than 24 hours&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"until=168h"&lt;/span&gt;       &lt;span class="c"&gt;# Dangling images older than 1 week&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"until=72h"&lt;/span&gt;       &lt;span class="c"&gt;# Everything older than 3 days&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution (Production Cleanup Strategy):&lt;/strong&gt; On CI/CD servers, set up a daily cron job: &lt;code&gt;0 2 * * * docker system prune -f&lt;/code&gt;. On production servers, only &lt;code&gt;docker container prune&lt;/code&gt; and &lt;code&gt;docker image prune&lt;/code&gt; are safe — &lt;strong&gt;never run &lt;code&gt;docker volume prune&lt;/code&gt; on production!&lt;/strong&gt; An accidental &lt;code&gt;docker volume prune&lt;/code&gt; has permanently deleted production data for multiple teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip (Interview — SIGTERM vs SIGKILL):&lt;/strong&gt; &lt;code&gt;docker stop&lt;/code&gt; first sends &lt;strong&gt;SIGTERM&lt;/strong&gt; — a graceful shutdown request. The application has 10 seconds to clean up (close connections, complete in-flight requests, flush data). If it doesn't exit within 10 seconds, &lt;strong&gt;SIGKILL&lt;/strong&gt; is sent — forced termination, no cleanup. &lt;code&gt;docker kill&lt;/code&gt; sends &lt;strong&gt;SIGKILL&lt;/strong&gt; directly — no grace period. Use &lt;code&gt;docker stop&lt;/code&gt; in production so applications can shut down properly. In Node.js: &lt;code&gt;process.on('SIGTERM', gracefulShutdown)&lt;/code&gt;. Exit codes: SIGTERM = 143, SIGKILL = 137.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Dockerfile Basics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a Dockerfile?
&lt;/h3&gt;

&lt;p&gt;A Dockerfile is a text file containing instructions for how to build an image. Think of it as a recipe card — ingredients and steps. Each instruction creates a layer, and layers together form the final Docker image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why build a custom image?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app requires a specific Node/Python version&lt;/li&gt;
&lt;li&gt;Custom environment variables or config are needed&lt;/li&gt;
&lt;li&gt;Pre-installed tools (curl, git, etc.) are required&lt;/li&gt;
&lt;li&gt;You need a production-ready, minimal image&lt;/li&gt;
&lt;li&gt;Security hardening — non-root user, read-only filesystem&lt;/li&gt;
&lt;li&gt;Reproducible builds — every developer gets the same environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where to put the Dockerfile
&lt;/h3&gt;

&lt;p&gt;Usually in the project root — the same directory as &lt;code&gt;package.json&lt;/code&gt; or &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
├── Dockerfile          ← here
├── Dockerfile.dev      ← for dev environment
├── Dockerfile.prod     ← for prod environment
├── .dockerignore       ← CRITICAL! Never forget this
├── package.json
├── src/
└── dist/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker build — Building an Image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build with current dir as context&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:latest &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Build with specific Dockerfile&lt;/span&gt;
docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.prod &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:prod &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Build from a remote Git repo&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp https://github.com/user/repo.git

&lt;span class="c"&gt;# Build with no cache (fresh build)&lt;/span&gt;
docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Build with build args&lt;/span&gt;
docker build &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Build with multiple tags&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:latest &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:v1.0.0 &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Build with verbose output (useful for CI)&lt;/span&gt;
docker build &lt;span class="nt"&gt;--progress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;plain &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; When you write &lt;code&gt;docker build -t myapp .&lt;/code&gt;, the &lt;code&gt;.&lt;/code&gt; (dot) is the build context — all files in this directory are sent to the Docker daemon. If your &lt;code&gt;node_modules&lt;/code&gt; is 500MB and you have no &lt;code&gt;.dockerignore&lt;/code&gt;, that entire directory gets sent. This is why &lt;code&gt;.dockerignore&lt;/code&gt; is critical.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  .dockerignore — Never Skip This
&lt;/h3&gt;

&lt;p&gt;This file works exactly like &lt;code&gt;.gitignore&lt;/code&gt; — it tells the Docker daemon which files to exclude from the build context.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Never let &lt;code&gt;.env&lt;/code&gt; files into a Docker image. If &lt;code&gt;AWS_SECRET_KEY&lt;/code&gt; or a database password ends up in an image, anyone can run &lt;code&gt;docker history myapp&lt;/code&gt; and see it — even if you deleted it in a later layer!&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# .dockerignore - put this in EVERY project!&lt;/span&gt;

&lt;span class="c"&gt;# Dependencies (will be installed inside container)&lt;/span&gt;
node_modules/
vendor/
__pycache__/
*.pyc
.pytest_cache/
venv/
.venv/

# Build outputs
dist/
build/
.next/
out/
target/

# Environment files (SECURITY RISK!)
.env
.env.local
.env.*.local
.env.development
.env.production
*.pem
*.key
secrets/

# Version control
.git/
.gitignore

&lt;span class="c"&gt;# Logs&lt;/span&gt;
*.log
logs/

# OS files
.DS_Store
Thumbs.db

&lt;span class="c"&gt;# IDE/Editor files&lt;/span&gt;
.idea/
.vscode/

# CI/CD &amp;amp; Docker meta
.github/
.dockerignore
Dockerfile*

# Documentation
README.md
*.md
docs/

# Test &amp;amp; Coverage
coverage/
.nyc_output
__tests__/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Build Cache
&lt;/h3&gt;

&lt;p&gt;Docker caches every layer. If a layer has not changed, Docker uses the cache — builds become very fast.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Layer unchanged&lt;/td&gt;
&lt;td&gt;Cache HIT&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layer changed&lt;/td&gt;
&lt;td&gt;Cache MISS&lt;/td&gt;
&lt;td&gt;Slow (rebuild)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After a cache miss, all subsequent layers&lt;/td&gt;
&lt;td&gt;Force rebuild&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--no-cache&lt;/code&gt; flag&lt;/td&gt;
&lt;td&gt;Skip all cache&lt;/td&gt;
&lt;td&gt;Slowest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; What changes least goes on top. What changes most (source code) goes at the bottom. If &lt;code&gt;package.json&lt;/code&gt; hasn't changed, npm install won't re-run — only source code copy will.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Image Naming Conventions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Tag formats&lt;/span&gt;
myapp:latest           &lt;span class="c"&gt;# ❌ Avoid in production!&lt;/span&gt;
myapp:v1.0.0           &lt;span class="c"&gt;# ✅ Semantic version (recommended)&lt;/span&gt;
myapp:1.0.0-alpine     &lt;span class="c"&gt;# ✅ Version + variant&lt;/span&gt;
myapp:2024-01-15       &lt;span class="c"&gt;# ✅ Date-based&lt;/span&gt;
myapp:abc1234          &lt;span class="c"&gt;# ✅ Git commit hash (traceable)&lt;/span&gt;
myapp:prod-v1.2.3      &lt;span class="c"&gt;# ✅ Environment-specific&lt;/span&gt;

&lt;span class="c"&gt;# With registry&lt;/span&gt;
docker.io/myuser/myapp:v1.0.0          &lt;span class="c"&gt;# Docker Hub&lt;/span&gt;
ghcr.io/myorg/myapp:v1.0.0            &lt;span class="c"&gt;# GitHub Container Registry&lt;/span&gt;
123456.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.0.0  &lt;span class="c"&gt;# AWS ECR&lt;/span&gt;

&lt;span class="c"&gt;# Build with git commit tag (common in CI/CD)&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Using &lt;code&gt;:latest&lt;/code&gt; in production means if you push a new image with &lt;code&gt;:latest&lt;/code&gt;, any server that restarts will pull the new image — without any warning! Always use specific version tags for reproducible deployments.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Dockerfile Instructions — Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  FROM — Every Dockerfile Starts Here
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic — always specify a version&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04&lt;/span&gt;

&lt;span class="c"&gt;# Specific digest (production best practice — immutable!)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu@sha256:abc123def456...&lt;/span&gt;

&lt;span class="c"&gt;# Multi-stage naming (used later with COPY --from)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="c"&gt;# ARG before FROM (dynamic base image version)&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_VERSION=18&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:${NODE_VERSION}-alpine&lt;/span&gt;

&lt;span class="c"&gt;# ✅ BEST: Pinned version + variant&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18.19.0-alpine3.19&lt;/span&gt;

&lt;span class="c"&gt;# ❌ BAD: Unpredictable, breaks builds&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip (Alpine vs Slim vs Full):&lt;/strong&gt; &lt;strong&gt;alpine&lt;/strong&gt;: uses musl libc, 5MB, fastest — but some native modules may not compile. &lt;strong&gt;slim&lt;/strong&gt;: Debian-based, ~75MB, more compatible. &lt;strong&gt;full&lt;/strong&gt;: Most compatible, but large image. For most Node.js apps: start with &lt;code&gt;node:18-alpine&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  LABEL — Image Metadata
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ BEST: All together in one layer&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; \&lt;/span&gt;
  maintainer="you@company.com" \
  version="1.0.0" \
  description="React DevOps Guide App" \
  org.opencontainers.image.source="https://github.com/user/repo" \
  org.opencontainers.image.created="2024-01-15" \
  org.opencontainers.image.revision="abc1234"

# Inspect labels
docker inspect myapp --format='{{json .Config.Labels}}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  RUN — Execute Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Shell form (runs in /bin/sh -c)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update

&lt;span class="c"&gt;# Exec form (no shell — preferred for clarity)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"apt-get"&lt;/span&gt;, &lt;span class="s2"&gt;"update"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# ❌ BAD: Multiple RUN = multiple layers = big image&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git

&lt;span class="c"&gt;# ✅ BEST: All in one RUN + clean cache in SAME layer&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;      curl &lt;span class="se"&gt;\
&lt;/span&gt;      git &lt;span class="se"&gt;\
&lt;/span&gt;      build-essential &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;   &lt;span class="c"&gt;# CRITICAL: clean apt cache!&lt;/span&gt;

&lt;span class="c"&gt;# Node.js&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Python&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Alpine (apk package manager)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    git &lt;span class="se"&gt;\
&lt;/span&gt;    bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; If you don't include &lt;code&gt;rm -rf /var/lib/apt/lists/*&lt;/code&gt;, the apt cache (which can be several MB) stays in the image — size grows unnecessarily. Always clean in the SAME RUN instruction, not in a separate one (cleaning in a separate layer does not reduce the layer size).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  CMD — Default Command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ Exec form (PREFERRED — no shell processing, better signal handling)&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0"]&lt;/span&gt;

&lt;span class="c"&gt;# ❌ Shell form (avoid — shell processing, harder to handle signals)&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; nginx -g "daemon off;"&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; node server.js&lt;/span&gt;

&lt;span class="c"&gt;# ⚠️ Multiple CMD — only the LAST one counts!&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "app.js"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]   # ← This one runs; the above is ignored&lt;/span&gt;

&lt;span class="c"&gt;# Override at runtime&lt;/span&gt;
docker run myapp node debug.js   &lt;span class="c"&gt;# CMD overridden&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ENTRYPOINT — Fixed Executable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Exec form (preferred)&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;

&lt;span class="c"&gt;# ENTRYPOINT + CMD combination (powerful pattern!)&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["server.js"]    # docker run myapp          → node server.js&lt;/span&gt;
                     &lt;span class="c"&gt;# docker run myapp debug.js  → node debug.js (CMD overridden)&lt;/span&gt;
                     &lt;span class="c"&gt;# docker run myapp --version → node --version&lt;/span&gt;

&lt;span class="c"&gt;# Common pattern: entrypoint script for setup logic&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; entrypoint.sh /entrypoint.sh&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /entrypoint.sh
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/entrypoint.sh"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["server"]&lt;/span&gt;

&lt;span class="c"&gt;# entrypoint.sh example:&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
set -e
echo "Starting container..."
# Run migrations, wait for DB, etc.
exec "$@"   # "$@" = pass CMD arguments through
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CMD vs ENTRYPOINT — Most Important Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;CMD&lt;/th&gt;
&lt;th&gt;ENTRYPOINT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Override easily?&lt;/td&gt;
&lt;td&gt;Yes — &lt;code&gt;docker run myapp npm test&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No — requires &lt;code&gt;--entrypoint&lt;/code&gt; flag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Purpose&lt;/td&gt;
&lt;td&gt;Provide default arguments&lt;/td&gt;
&lt;td&gt;Set a fixed executable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When used together&lt;/td&gt;
&lt;td&gt;CMD = default args for ENTRYPOINT&lt;/td&gt;
&lt;td&gt;ENTRYPOINT = command, CMD = args&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Override example&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker run myapp bash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker run --entrypoint bash myapp&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use case&lt;/td&gt;
&lt;td&gt;Flexible containers (same image for dev/prod)&lt;/td&gt;
&lt;td&gt;Single-purpose containers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip (Interview Question):&lt;/strong&gt; "What is the difference between CMD and ENTRYPOINT?" — Short answer: CMD is easily overridden, ENTRYPOINT is not. Use them together — ENTRYPOINT as the executable, CMD as the default arguments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  COPY and ADD
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# COPY — preferred for most cases&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json /app/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json /app/&lt;/span&gt;

&lt;span class="c"&gt;# With ownership (chown)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node . /app/&lt;/span&gt;

&lt;span class="c"&gt;# From a multi-stage build (POWERFUL!)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist /usr/share/nginx/html/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/.next/standalone ./&lt;/span&gt;

&lt;span class="c"&gt;# ADD — supports URLs and auto-extracts tar files&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://example.com/file.tar.gz /tmp/        # ❌ Prefer curl in RUN&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; myarchive.tar.gz /app/                       # ✅ Auto-extracts tar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;COPY&lt;/th&gt;
&lt;th&gt;ADD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local files&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Download from URLs&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-extract tar&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching&lt;/td&gt;
&lt;td&gt;Predictable&lt;/td&gt;
&lt;td&gt;Less predictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended&lt;/td&gt;
&lt;td&gt;For most cases&lt;/td&gt;
&lt;td&gt;Only for tar extraction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  WORKDIR, ENV, ARG
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# WORKDIR — sets working directory&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="c"&gt;# ❌ BAD: Use cd command&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt;  &lt;span class="c"&gt;# Only valid for this RUN&lt;/span&gt;

&lt;span class="c"&gt;# ✅ GOOD: Use WORKDIR&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;              &lt;span class="c"&gt;# Runs in /app&lt;/span&gt;

&lt;span class="c"&gt;# ENV — environment variables (build + runtime)&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production \&lt;/span&gt;
    PORT=3000 \
    LOG_LEVEL=info \
    TZ=Asia/Kolkata

&lt;span class="c"&gt;# Override at runtime&lt;/span&gt;
docker run -e NODE_ENV=development myapp
&lt;span class="c"&gt;# ⚠️ Never put secrets in ENV — visible in docker inspect!&lt;/span&gt;

&lt;span class="c"&gt;# ARG — build-time arguments (NOT available at runtime)&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_VERSION=18&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:${NODE_VERSION}-alpine&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; BUILD_DATE&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; GIT_COMMIT&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; APP_VERSION=1.0.0&lt;/span&gt;

&lt;span class="c"&gt;# Pass at build time&lt;/span&gt;
docker build \
  --build-arg NODE_VERSION=20 \
  --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) \
  -t myapp .

# Convert ARG to ENV (to make it available at runtime)
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL=$REACT_APP_API_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;ARG&lt;/th&gt;
&lt;th&gt;ENV&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Available at&lt;/td&gt;
&lt;td&gt;Build time only&lt;/td&gt;
&lt;td&gt;Build + Runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persists in container&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can use before FROM&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visibility&lt;/td&gt;
&lt;td&gt;In layer history&lt;/td&gt;
&lt;td&gt;In &lt;code&gt;docker inspect&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  EXPOSE, VOLUME, USER
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# EXPOSE — documentation only (does NOT actually open the port!)&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80 443&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080/tcp&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 53/udp&lt;/span&gt;

&lt;span class="c"&gt;# Actual port publishing requires -p flag at runtime:&lt;/span&gt;
docker run -p 8080:3000 myapp   &lt;span class="c"&gt;# host:container&lt;/span&gt;

&lt;span class="c"&gt;# VOLUME — declare mount points&lt;/span&gt;
&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; /app/data&lt;/span&gt;
&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; /var/lib/postgresql/data&lt;/span&gt;
&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; ["/app/logs", "/app/uploads"]&lt;/span&gt;

&lt;span class="c"&gt;# In production, use named volumes:&lt;/span&gt;
docker run -v postgres_data:/var/lib/postgresql/data postgres

&lt;span class="c"&gt;# USER — run as non-root (security!)&lt;/span&gt;
&lt;span class="c"&gt;# ❌ BAD: Running as root (default if USER not specified)&lt;/span&gt;

&lt;span class="c"&gt;# ✅ Method 1: Named user&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nt"&gt;-r&lt;/span&gt; appuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; useradd &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; appuser appuser
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="c"&gt;# ✅ Method 2: Numeric UID (more secure)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 nodejs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 nextjs
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; 1001&lt;/span&gt;

&lt;span class="c"&gt;# ✅ Node.js: Use the built-in 'node' user&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=node:node . .&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Kubernetes and AWS ECS policies typically prohibit containers running as root. Always specify a non-root USER. In Kubernetes, set &lt;code&gt;runAsNonRoot: true&lt;/code&gt; in PodSecurityPolicy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  HEALTHCHECK
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# HTTP health check (common for web apps)&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --start-period=5s --retries=3 \&lt;/span&gt;
  CMD curl -f http://localhost:3000/health || exit 1

&lt;span class="c"&gt;# Node.js health check (when curl is not available)&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s --start-period=10s --retries=3 \&lt;/span&gt;
  CMD node -e "require('http').get('http://localhost:3000/health', r =&amp;gt; r.statusCode === 200 ? process.exit(0) : process.exit(1))"

# Disable inherited health check
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; NONE&lt;/span&gt;

&lt;span class="c"&gt;# Options:&lt;/span&gt;
&lt;span class="c"&gt;# --interval=30s    → check every 30 seconds (default: 30s)&lt;/span&gt;
&lt;span class="c"&gt;# --timeout=3s      → fail if no response within 3s (default: 30s)&lt;/span&gt;
&lt;span class="c"&gt;# --start-period=5s → 5s grace period at start (container boot time)&lt;/span&gt;
&lt;span class="c"&gt;# --retries=3       → 3 consecutive failures = unhealthy&lt;/span&gt;

&lt;span class="c"&gt;# Check status&lt;/span&gt;
docker inspect --format='{{.State.Health.Status}}' container_name
docker ps   &lt;span class="c"&gt;# STATUS column shows (healthy) or (unhealthy)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shell Form vs Exec Form — Signal Handling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Shell form: runs via /bin/sh -c&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; nginx -g "daemon off;"&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; node server.js&lt;/span&gt;

&lt;span class="c"&gt;# Exec form: runs directly (no shell)&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;

&lt;span class="c"&gt;# KEY DIFFERENCE: Signal handling!&lt;/span&gt;
&lt;span class="c"&gt;# Shell form:&lt;/span&gt;
&lt;span class="c"&gt;#   /bin/sh (PID 1) → nginx (child process)&lt;/span&gt;
&lt;span class="c"&gt;#   SIGTERM goes to /bin/sh, not nginx → dirty shutdown!&lt;/span&gt;

&lt;span class="c"&gt;# Exec form:&lt;/span&gt;
&lt;span class="c"&gt;#   nginx (PID 1) directly&lt;/span&gt;
&lt;span class="c"&gt;#   SIGTERM goes directly to nginx → graceful shutdown!&lt;/span&gt;

&lt;span class="c"&gt;# RULE: ALWAYS use exec form in CMD and ENTRYPOINT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip (Interview):&lt;/strong&gt; "What is the difference between shell form and exec form?" — Shell form runs via &lt;code&gt;/bin/sh -c&lt;/code&gt; (does not become PID 1), exec form executes directly (becomes PID 1). In production, exec form is preferred because signals are properly received.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Writing Efficient Dockerfiles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before vs After — 1.5 GB to 180 MB
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before (1.5 GB image):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs npm
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; node server.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Problems: Ubuntu base (~80MB), Node+npm via apt (~200MB), multiple apt layers, all &lt;code&gt;node_modules&lt;/code&gt; included, no cache optimization, running as root, shell form CMD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After (180 MB image):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits: Alpine base (~5MB), official Node image, layered cache optimization, production deps only, non-root user, exec CMD form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer Ordering — Cache Optimization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ BAD ORDER: package.json copied with source code&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                    # Source code AND package.json together&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci                  &lt;span class="c"&gt;# Runs on every code change!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;

&lt;span class="c"&gt;# ✅ OPTIMAL ORDER: What changes least goes first&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine          # Layer 1: base image (almost always cached)&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app                 # Layer 2: almost always cached&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./        # Layer 3: rebuilt only when package.json changes&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci                   &lt;span class="c"&gt;# Layer 4: rebuilt only when package.json changes&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                     # Layer 5: rebuilt on any code change&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build            &lt;span class="c"&gt;# Layer 6: rebuilt on any code change&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;

&lt;span class="c"&gt;# RESULT:&lt;/span&gt;
&lt;span class="c"&gt;# If only src/App.js changed:&lt;/span&gt;
&lt;span class="c"&gt;# → Layers 1-4: CACHE HIT (npm ci skipped!)&lt;/span&gt;
&lt;span class="c"&gt;# → Layers 5-6: rebuild&lt;/span&gt;
&lt;span class="c"&gt;# Build time: 10s instead of 60s!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Thumb rule: "What changes least goes on top." Order: Base image → System deps → Package manifest (package.json/requirements.txt) → Dependencies install → Source code copy → Build → Configuration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Base Image Choices
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Base Image&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Pros/Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ubuntu:22.04&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~80MB&lt;/td&gt;
&lt;td&gt;Legacy apps needing apt&lt;/td&gt;
&lt;td&gt;Familiar, large&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debian:slim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~75MB&lt;/td&gt;
&lt;td&gt;Debian apps, smaller&lt;/td&gt;
&lt;td&gt;Compatible, slim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;alpine:3.19&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~5MB&lt;/td&gt;
&lt;td&gt;Smallest possible&lt;/td&gt;
&lt;td&gt;musl libc issues sometimes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;node:18-alpine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~50MB&lt;/td&gt;
&lt;td&gt;Node.js apps&lt;/td&gt;
&lt;td&gt;Best for Node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python:3.11-slim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~130MB&lt;/td&gt;
&lt;td&gt;Python apps&lt;/td&gt;
&lt;td&gt;Best for Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gcr.io/distroless/nodejs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~30MB&lt;/td&gt;
&lt;td&gt;Security-focused&lt;/td&gt;
&lt;td&gt;No shell — very secure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scratch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0MB&lt;/td&gt;
&lt;td&gt;Static Go/Rust binaries&lt;/td&gt;
&lt;td&gt;Smallest possible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Security Best Practices
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Pin specific versions&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18.19.0-alpine3.19   # ✅ Exact version&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:latest                # ❌ Unpredictable&lt;/span&gt;

&lt;span class="c"&gt;# 2. Non-root user (always!)&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;

&lt;span class="c"&gt;# 3. NEVER put secrets in ENV/ARG permanently&lt;/span&gt;
&lt;span class="c"&gt;# ❌ WRONG — visible in docker history!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret123 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./setup.sh

&lt;span class="c"&gt;# ✅ RIGHT — use runtime env vars&lt;/span&gt;
docker run -e API_KEY=secret123 myapp

&lt;span class="c"&gt;# 4. Don't install unnecessary packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# 5. Read-only filesystem (at runtime)&lt;/span&gt;
docker run --read-only \
    --tmpfs /tmp \
    --tmpfs /app/logs \
    myapp

&lt;span class="c"&gt;# 6. Drop all capabilities (at runtime)&lt;/span&gt;
docker run --cap-drop ALL \
    --cap-add NET_BIND_SERVICE \
    myapp

&lt;span class="c"&gt;# 7. Scan images for vulnerabilities&lt;/span&gt;
docker scout cves myapp:latest
trivy image myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (Production Security Checklist):&lt;/strong&gt; Non-root USER | &lt;code&gt;.dockerignore&lt;/code&gt; with &lt;code&gt;.env&lt;/code&gt; | No secrets in Dockerfile | Specific base image version | HEALTHCHECK defined | Image vulnerability scan in CI/CD | Read-only filesystem where possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  npm install vs npm ci
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;In Dockerfile?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Development, install from package.json&lt;/td&gt;
&lt;td&gt;Avoid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm ci&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exact install from package-lock.json&lt;/td&gt;
&lt;td&gt;Always use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;npm ci --only=production&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exclude devDependencies&lt;/td&gt;
&lt;td&gt;For production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Multi-Stage Builds
&lt;/h2&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
Multi-stage builds keep the build toolchain in a throw-away stage and ship only the compiled artifact in a minimal final image, cutting image size by 80–90%.





&lt;h3&gt;
  
  
  Why Multi-Stage Builds?
&lt;/h3&gt;

&lt;p&gt;The problem: build tools are needed at build time (compiler, test runner, devDependencies) but should not be in the production image. Without multi-stage builds, everything ends up in the final image — size explodes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without Multi-Stage&lt;/th&gt;
&lt;th&gt;With Multi-Stage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build tools included (1.2 GB)&lt;/td&gt;
&lt;td&gt;Only production artifacts (120 MB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;devDependencies included&lt;/td&gt;
&lt;td&gt;Build tools discarded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source code included&lt;/td&gt;
&lt;td&gt;Source code discarded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security risk (unnecessary tools)&lt;/td&gt;
&lt;td&gt;Minimal attack surface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow to push/pull&lt;/td&gt;
&lt;td&gt;Fast to push/pull&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# BEFORE multi-stage: build tools in final image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;      &lt;span class="c"&gt;# devDependencies too — 500MB!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;span class="c"&gt;# Final image: ~1.2 GB&lt;/span&gt;

&lt;span class="c"&gt;# AFTER multi-stage: only production artifacts&lt;/span&gt;
&lt;span class="c"&gt;# ---- Stage 1: Builder ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci              &lt;span class="c"&gt;# ALL deps (devDeps for build)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build       &lt;span class="c"&gt;# Create production bundle&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 2: Runner (final image) ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production   &lt;span class="c"&gt;# Production deps only&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist   # Only built files!&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;span class="c"&gt;# Final image: ~120 MB  (90% size reduction!)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;COPY --from=builder&lt;/code&gt; only copies the files you specify — not the entire builder stage. Everything else (node_modules, devDeps, source code, compiler) is automatically discarded. The final image contains only the content from the LAST &lt;code&gt;FROM&lt;/code&gt; onwards.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  React App — Production-Ready Multi-Stage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# React App — Production Dockerfile&lt;/span&gt;
&lt;span class="c"&gt;# Multi-stage: Node builder → Nginx server&lt;/span&gt;
&lt;span class="c"&gt;# Final size: ~25-30MB&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 1: Build ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL=https://api.myapp.com&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_VERSION=1.0.0&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL=$REACT_APP_API_URL \&lt;/span&gt;
    REACT_APP_ENV=$REACT_APP_ENV \
    REACT_APP_VERSION=$REACT_APP_VERSION

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;span class="c"&gt;# Output: /app/build/&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 2: Serve ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:1.25-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; maintainer="you@company.com" \&lt;/span&gt;
      version="1.0.0" \
      description="React App Production Image"

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/build /usr/share/nginx/html&lt;/span&gt;

&lt;span class="c"&gt;# Custom Nginx config (required for SPA routing!)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nginx:nginx /usr/share/nginx/html &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nginx:nginx /var/cache/nginx &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;touch&lt;/span&gt; /var/run/nginx.pid &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nginx:nginx /var/run/nginx.pid

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nginx&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --start-period=5s --retries=3 \&lt;/span&gt;
  CMD wget -qO- http://localhost/health || exit 1

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# nginx.conf&lt;/span&gt;
&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle React Router (SPA routing!)&lt;/span&gt;
    &lt;span class="c1"&gt;# Without this, refreshing /about gives a 404!&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Long-term cache for static assets&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)&lt;/span&gt;$ &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;1y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"public,&lt;/span&gt; &lt;span class="s"&gt;immutable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Health check endpoint&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/health&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="s"&gt;"healthy&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;n"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/plain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Security headers&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;"SAMEORIGIN"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;"nosniff"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-XSS-Protection&lt;/span&gt; &lt;span class="s"&gt;"1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;mode=block"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Gzip compression&lt;/span&gt;
    &lt;span class="kn"&gt;gzip&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;gzip_types&lt;/span&gt; &lt;span class="nc"&gt;text/plain&lt;/span&gt; &lt;span class="nc"&gt;text/css&lt;/span&gt; &lt;span class="nc"&gt;application/json&lt;/span&gt;
               &lt;span class="nc"&gt;application/javascript&lt;/span&gt; &lt;span class="nc"&gt;text/xml&lt;/span&gt; &lt;span class="nc"&gt;application/xml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;gzip_min_length&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Go Application — Scratch Image (10MB!)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ---- Stage 1: Go Binary Build ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.21-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Static binary build (no external dependencies)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 &lt;span class="se"&gt;\
&lt;/span&gt;    go build &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;-ldflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-w -s"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-installsuffix&lt;/span&gt; cgo &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;-o&lt;/span&gt; app .

&lt;span class="c"&gt;# ---- Stage 2: Scratch (empty base!) ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;

&lt;span class="c"&gt;# SSL certificates (for HTTPS calls)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/&lt;/span&gt;

&lt;span class="c"&gt;# Only the binary&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/app /app&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/app"]&lt;/span&gt;

&lt;span class="c"&gt;# Final image: ~8-10MB (just the binary!)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Java Spring Boot Multi-Stage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ---- Stage 1: Maven Build ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3.9-openjdk-17-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy pom.xml first (dependency caching!)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;mvn dependency:go-offline &lt;span class="nt"&gt;-B&lt;/span&gt;   &lt;span class="c"&gt;# Download all dependencies&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;mvn clean package &lt;span class="nt"&gt;-DskipTests&lt;/span&gt; &lt;span class="nt"&gt;-B&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 2: JRE Only (no JDK!) ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;eclipse-temurin:17-jre-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 spring &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 spring
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; spring&lt;/span&gt;

&lt;span class="c"&gt;# Only the JAR (not Maven, not JDK)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/target/*.jar app.jar&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=10s --start-period=30s --retries=3 \&lt;/span&gt;
  CMD curl -f http://localhost:8080/actuator/health || exit 1

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", \&lt;/span&gt;
  "-XX:+UseContainerSupport", \
  "-XX:MaxRAMPercentage=75.0", \
  "-jar", "app.jar"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building Specific Stages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build up to a specific stage (useful for debugging!)&lt;/span&gt;
docker build &lt;span class="nt"&gt;--target&lt;/span&gt; builder &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:builder &lt;span class="nb"&gt;.&lt;/span&gt;
docker build &lt;span class="nt"&gt;--target&lt;/span&gt; production &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:prod &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Open a shell in the builder stage&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp:builder sh

&lt;span class="c"&gt;# Separate development vs production builds&lt;/span&gt;
docker build &lt;span class="nt"&gt;--target&lt;/span&gt; development &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:dev &lt;span class="nb"&gt;.&lt;/span&gt;
docker build &lt;span class="nt"&gt;--target&lt;/span&gt; production &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:prod &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Dockerfile Templates Library
&lt;/h2&gt;

&lt;p&gt;These templates are copy-paste ready for production use. All are fully commented and follow security best practices. Customize the &lt;code&gt;EXPOSE&lt;/code&gt; port, user names, and &lt;code&gt;CMD&lt;/code&gt; for your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template 1: React App (Production-Ready)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# React App — Production Dockerfile&lt;/span&gt;
&lt;span class="c"&gt;# Multi-stage: Node builder → Nginx server&lt;/span&gt;
&lt;span class="c"&gt;# Final size: ~25-30MB&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 1: Build ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL=https://api.myapp.com&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_VERSION=1.0.0&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL=$REACT_APP_API_URL \&lt;/span&gt;
    REACT_APP_ENV=$REACT_APP_ENV \
    REACT_APP_VERSION=$REACT_APP_VERSION

&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;span class="c"&gt;# Output: /app/build/&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 2: Serve ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx:1.25-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; maintainer="you@company.com" \&lt;/span&gt;
      version="1.0.0" \
      description="React App Production Image"

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/build /usr/share/nginx/html&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nginx:nginx /usr/share/nginx/html &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nginx:nginx /var/cache/nginx &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;touch&lt;/span&gt; /var/run/nginx.pid &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nginx:nginx /var/run/nginx.pid

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nginx&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --start-period=5s --retries=3 \&lt;/span&gt;
  CMD wget -qO- http://localhost/health || exit 1

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;

&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# Build commands:&lt;/span&gt;
&lt;span class="c"&gt;# docker build -t myreactapp:latest .&lt;/span&gt;
&lt;span class="c"&gt;# docker build --build-arg REACT_APP_API_URL=https://prod.api.com -t myreactapp:prod .&lt;/span&gt;
&lt;span class="c"&gt;# docker run -p 8080:80 myreactapp:latest&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Template 2: Node.js Express API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# Node.js Express API — Production Dockerfile&lt;/span&gt;
&lt;span class="c"&gt;# Features: dumb-init, TypeScript support,&lt;/span&gt;
&lt;span class="c"&gt;#           non-root user, health check&lt;/span&gt;
&lt;span class="c"&gt;# Final size: ~80-120MB&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="c"&gt;# dumb-init: proper PID 1 handling + signal forwarding&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; dumb-init

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 1: Dependencies ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="c"&gt;# ---- Stage 2: Build (for TypeScript projects) ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No build step found, skipping..."&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 3: Production ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; maintainer="you@company.com" \&lt;/span&gt;
      description="Express API Production Image"

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist 2&amp;gt;/dev/null || true&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/src ./src 2&amp;gt;/dev/null || true&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 nodejs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 nodeuser
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; nodeuser:nodejs /app

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nodeuser&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --start-period=10s --retries=3 \&lt;/span&gt;
  CMD node -e "require('http').get('http://localhost:3000/health', (r) =&amp;gt; { \
    r.statusCode === 200 ? process.exit(0) : process.exit(1); \
  }).on('error', () =&amp;gt; process.exit(1))"

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dumb-init", "--"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "src/server.js"]&lt;/span&gt;

&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# Build commands:&lt;/span&gt;
&lt;span class="c"&gt;# docker build -t myapi:latest .&lt;/span&gt;
&lt;span class="c"&gt;# docker run -p 3000:3000 -e DB_URL=mongodb://... myapi:latest&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# For TypeScript projects, change CMD to:&lt;/span&gt;
&lt;span class="c"&gt;# CMD ["node", "dist/server.js"]&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Template 3: Python Flask App
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# Python Flask App — Production Dockerfile&lt;/span&gt;
&lt;span class="c"&gt;# Features: Gunicorn WSGI, non-root user,&lt;/span&gt;
&lt;span class="c"&gt;#           multi-stage, health check&lt;/span&gt;
&lt;span class="c"&gt;# Final size: ~130-160MB&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get upgrade &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 appuser

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 2: Dependencies ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# ---- Stage 3: Production ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONDONTWRITEBYTECODE=1 \&lt;/span&gt;
    PYTHONUNBUFFERED=1 \
    FLASK_ENV=production \
    PORT=5000

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; maintainer="you@company.com" \&lt;/span&gt;
      description="Flask API Production Image"

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /root/.local /home/appuser/.local&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH=/home/appuser/.local/bin:$PATH&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appgroup . .&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s --start-period=10s --retries=3 \&lt;/span&gt;
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')" \
      || exit 1

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "-m", "gunicorn", \&lt;/span&gt;
     "--bind", "0.0.0.0:5000", \
     "--workers", "4", \
     "--timeout", "120", \
     "--access-logfile", "-", \
     "--error-logfile", "-", \
     "app:app"]

# ============================================
# requirements.txt must include:
# Flask==3.0.0
&lt;span class="c"&gt;# gunicorn==21.2.0&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Template 4: Python FastAPI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# FastAPI App — Production Dockerfile&lt;/span&gt;
&lt;span class="c"&gt;# Features: Uvicorn ASGI, non-root user,&lt;/span&gt;
&lt;span class="c"&gt;#           async support, health check&lt;/span&gt;
&lt;span class="c"&gt;# Final size: ~130-150MB&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; curl &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 appuser

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONDONTWRITEBYTECODE=1 \&lt;/span&gt;
    PYTHONUNBUFFERED=1

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; maintainer="you@company.com" \&lt;/span&gt;
      description="FastAPI Production Image"

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appgroup . .&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --start-period=10s --retries=3 \&lt;/span&gt;
  CMD curl -f http://localhost:8000/health || exit 1

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", \&lt;/span&gt;
     "--host", "0.0.0.0", \
     "--port", "8000", \
     "--workers", "4", \
     "--loop", "uvloop", \
     "--access-log"]

# ============================================
# requirements.txt must include:
# fastapi==0.109.0
&lt;span class="c"&gt;# uvicorn[standard]==0.27.0&lt;/span&gt;
&lt;span class="c"&gt;# uvloop==0.19.0&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# main.py health endpoint:&lt;/span&gt;
&lt;span class="c"&gt;# @app.get("/health")&lt;/span&gt;
&lt;span class="c"&gt;# async def health(): return {"status": "healthy"}&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Template 5: Next.js Production
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# Next.js App — Production Dockerfile&lt;/span&gt;
&lt;span class="c"&gt;# Features: Standalone output, optimized,&lt;/span&gt;
&lt;span class="c"&gt;#           non-root, telemetry disabled&lt;/span&gt;
&lt;span class="c"&gt;# Final size: ~100-150MB&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; libc6-compat
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 2: Dependencies ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="c"&gt;# ---- Stage 3: Build ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_TELEMETRY_DISABLED=1&lt;/span&gt;

&lt;span class="c"&gt;# REQUIRED: next.config.js must have output: 'standalone'&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# ---- Stage 4: Runner ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_TELEMETRY_DISABLED=1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=3000&lt;/span&gt;

&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; maintainer="you@company.com" \&lt;/span&gt;
      description="Next.js Production Image"

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 nodejs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 nextjs

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/public ./public&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/standalone ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nextjs&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s --start-period=10s --retries=3 \&lt;/span&gt;
  CMD node -e "require('http').get('http://localhost:3000/api/health', r =&amp;gt; \
    r.statusCode === 200 ? process.exit(0) : process.exit(1))"

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;

&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# REQUIRED: next.config.js must include:&lt;/span&gt;
&lt;span class="c"&gt;# const nextConfig = {&lt;/span&gt;
&lt;span class="c"&gt;#   output: 'standalone',&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;
&lt;span class="c"&gt;# module.exports = nextConfig&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; For Next.js Docker images, &lt;code&gt;output: 'standalone'&lt;/code&gt; in &lt;code&gt;next.config.js&lt;/code&gt; is &lt;strong&gt;required&lt;/strong&gt;. Without it, the &lt;code&gt;.next/standalone&lt;/code&gt; folder will not be created and &lt;code&gt;COPY&lt;/code&gt; will fail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Quick Reference: Which Template to Use
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project Type&lt;/th&gt;
&lt;th&gt;Template&lt;/th&gt;
&lt;th&gt;Final Size&lt;/th&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;React (CRA, Vite)&lt;/td&gt;
&lt;td&gt;Template 1&lt;/td&gt;
&lt;td&gt;~25-30MB&lt;/td&gt;
&lt;td&gt;Nginx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js / Express / Fastify&lt;/td&gt;
&lt;td&gt;Template 2&lt;/td&gt;
&lt;td&gt;~80-120MB&lt;/td&gt;
&lt;td&gt;Node + dumb-init&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python Flask (sync)&lt;/td&gt;
&lt;td&gt;Template 3&lt;/td&gt;
&lt;td&gt;~130-160MB&lt;/td&gt;
&lt;td&gt;Gunicorn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python FastAPI (async)&lt;/td&gt;
&lt;td&gt;Template 4&lt;/td&gt;
&lt;td&gt;~130-150MB&lt;/td&gt;
&lt;td&gt;Uvicorn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js (full-stack)&lt;/td&gt;
&lt;td&gt;Template 5&lt;/td&gt;
&lt;td&gt;~100-150MB&lt;/td&gt;
&lt;td&gt;Node standalone&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note (Common Patterns in Every Template):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-root USER — mandatory for security&lt;/li&gt;
&lt;li&gt;HEALTHCHECK — so Docker/Kubernetes knows container health&lt;/li&gt;
&lt;li&gt;Multi-stage builds — build tools not in final image&lt;/li&gt;
&lt;li&gt;Cache optimization — package.json first, source code second&lt;/li&gt;
&lt;li&gt;Exec form CMD — proper signal handling&lt;/li&gt;
&lt;li&gt;Specific versions — reproducible builds&lt;/li&gt;
&lt;li&gt;LABEL metadata — for traceability&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Networking Fundamentals
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Containers Need Networking
&lt;/h3&gt;

&lt;p&gt;By default, every container runs in an isolated environment — its own filesystem, its own process space, and its own network namespace. However, real-world applications require containers to communicate with each other, connect to external databases, and serve clients. Understanding Docker's networking model is therefore essential.&lt;/p&gt;

&lt;p&gt;Scenarios that require networking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Container-to-Container:&lt;/strong&gt; A frontend container needs to communicate with a backend API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container to External Services:&lt;/strong&gt; An application container needs to connect to AWS RDS or a third-party API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External Clients to Containers:&lt;/strong&gt; A browser sends a request and a container serves it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-host Communication:&lt;/strong&gt; Containers on different servers still communicate with each other (Overlay networks)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Container Networking Model (CNM)
&lt;/h3&gt;

&lt;p&gt;Docker defines a standard networking architecture called the &lt;strong&gt;Container Networking Model (CNM)&lt;/strong&gt;. It has three core concepts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Real-World Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Isolated communication group where containers can talk to each other&lt;/td&gt;
&lt;td&gt;An office floor — only people on that floor can communicate directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Endpoint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A virtual NIC (network interface card) for a container on a network&lt;/td&gt;
&lt;td&gt;An employee's desk phone extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sandbox&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Container's isolated network namespace — its own IP, routing table, and DNS&lt;/td&gt;
&lt;td&gt;An employee's complete communication profile&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;libnetwork — Docker's Networking Brain.&lt;/strong&gt; CNM is implemented by &lt;strong&gt;libnetwork&lt;/strong&gt; — Docker's networking subsystem. It is written in Go and supports a pluggable driver architecture. This means you can use bridge, overlay, macvlan — any driver — on top of libnetwork.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Network Driver Architecture:&lt;/strong&gt; libnetwork uses a plugin system. Built-in drivers include: &lt;code&gt;bridge&lt;/code&gt;, &lt;code&gt;host&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt;, &lt;code&gt;overlay&lt;/code&gt;, and &lt;code&gt;macvlan&lt;/code&gt;. Third-party drivers can also be installed (e.g., Weave, Calico).&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Networking Model — Architecture Overview
&lt;/h3&gt;

&lt;p&gt;Internet traffic arrives at the host → is forwarded to the &lt;code&gt;docker0&lt;/code&gt; bridge → passes through to Container A on port 80 via host port 8080. Container A and Container B can communicate directly with each other using their &lt;code&gt;172.17.x.x&lt;/code&gt; IP addresses through the bridge.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;veth Pair — The Magic Behind Container Networking.&lt;/strong&gt; When Docker starts a container, it creates a &lt;strong&gt;virtual ethernet pair (veth)&lt;/strong&gt; — like two ends of a virtual cable. One end lives inside the container (&lt;code&gt;eth0&lt;/code&gt;) and the other end attaches to the &lt;code&gt;docker0&lt;/code&gt; bridge. Data flows seamlessly from one end to the other.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Network Types — Bridge, Host, None, Overlay, Macvlan
&lt;/h2&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;
The docker0 bridge connects containers on the same host using private IPs; a published port (-p 8080:80) uses iptables NAT to forward external traffic from the host into a specific container.





&lt;h3&gt;
  
  
  Bridge Network — The Most Common
&lt;/h3&gt;

&lt;p&gt;When you start a container without the &lt;code&gt;--network&lt;/code&gt; flag, it automatically joins the &lt;strong&gt;default bridge network&lt;/strong&gt; (&lt;code&gt;docker0&lt;/code&gt;). However, the default bridge has a significant limitation — &lt;strong&gt;DNS resolution does not work&lt;/strong&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;Common Beginner Mistake — No DNS on Default Bridge!&lt;/strong&gt; On the default bridge, containers cannot find each other by name — only by IP address. The IP address can change on every restart. Always use a &lt;strong&gt;user-defined bridge&lt;/strong&gt;!&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Default bridge — no DNS!&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; db mysql:8        &lt;span class="c"&gt;# joins default bridge&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; myapp myimage     &lt;span class="c"&gt;# joins default bridge&lt;/span&gt;
&lt;span class="c"&gt;# myapp CANNOT connect to "db" by hostname!&lt;/span&gt;
&lt;span class="c"&gt;# Must use IP: mysql -h 172.17.0.2 (fragile!)&lt;/span&gt;

&lt;span class="c"&gt;# User-defined bridge — DNS built-in!&lt;/span&gt;
docker network create mynetwork

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; db &lt;span class="nt"&gt;--network&lt;/span&gt; mynetwork mysql:8
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; myapp &lt;span class="nt"&gt;--network&lt;/span&gt; mynetwork myimage
&lt;span class="c"&gt;# Now myapp can connect directly using the "db" hostname!&lt;/span&gt;
&lt;span class="c"&gt;# MONGODB_URI=mongodb://db:27017/mydb  ← works!&lt;/span&gt;

&lt;span class="c"&gt;# Create a user-defined bridge with options&lt;/span&gt;
docker network create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; bridge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet&lt;/span&gt; 172.20.0.0/16 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gateway&lt;/span&gt; 172.20.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  my-custom-network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Default Bridge&lt;/th&gt;
&lt;th&gt;User-Defined Bridge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DNS Resolution (by name)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (container name!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic Discovery&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended?&lt;/td&gt;
&lt;td&gt;Legacy only&lt;/td&gt;
&lt;td&gt;Always use this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Isolation&lt;/td&gt;
&lt;td&gt;All containers share it&lt;/td&gt;
&lt;td&gt;Per-network isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connect/disconnect from running container&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Host Network — Maximum Performance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container shares the host's network namespace&lt;/span&gt;
docker run &lt;span class="nt"&gt;--network&lt;/span&gt; host nginx
&lt;span class="c"&gt;# nginx now listens directly on host port 80&lt;/span&gt;
&lt;span class="c"&gt;# -p flag is not needed (and does not work)&lt;/span&gt;

&lt;span class="c"&gt;# Benefits:&lt;/span&gt;
&lt;span class="c"&gt;# - Best performance: no NAT overhead&lt;/span&gt;
&lt;span class="c"&gt;# - Low latency: direct kernel network stack access&lt;/span&gt;

&lt;span class="c"&gt;# Drawbacks:&lt;/span&gt;
&lt;span class="c"&gt;# - No isolation: container can see all host interfaces&lt;/span&gt;
&lt;span class="c"&gt;# - Port conflicts: container and host services cannot share the same port&lt;/span&gt;

&lt;span class="c"&gt;# Use cases:&lt;/span&gt;
&lt;span class="c"&gt;# - High-performance networking applications&lt;/span&gt;
&lt;span class="c"&gt;# - Monitoring agents (Prometheus node_exporter)&lt;/span&gt;
&lt;span class="c"&gt;# - Network packet inspection tools&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  None Network — Complete Isolation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# No networking at all — completely isolated&lt;/span&gt;
docker run &lt;span class="nt"&gt;--network&lt;/span&gt; none myapp

&lt;span class="c"&gt;# Inside the container:&lt;/span&gt;
&lt;span class="c"&gt;# - No internet access&lt;/span&gt;
&lt;span class="c"&gt;# - No communication with other containers&lt;/span&gt;
&lt;span class="c"&gt;# - Only loopback (127.0.0.1) is available&lt;/span&gt;

&lt;span class="c"&gt;# Use cases:&lt;/span&gt;
&lt;span class="c"&gt;# - Batch data processing jobs (file input/output only)&lt;/span&gt;
&lt;span class="c"&gt;# - Security-sensitive computations&lt;/span&gt;
&lt;span class="c"&gt;# - Testing network-agnostic code&lt;/span&gt;
&lt;span class="c"&gt;# - Malware analysis environments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Overlay Network — Multi-Host Communication (Swarm)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Multi-host networking — containers on different servers communicate with each other&lt;/span&gt;
&lt;span class="c"&gt;# Requires: Docker Swarm mode (or Kubernetes)&lt;/span&gt;

&lt;span class="c"&gt;# Initialize Swarm mode&lt;/span&gt;
docker swarm init

&lt;span class="c"&gt;# Create an overlay network&lt;/span&gt;
docker network create &lt;span class="nt"&gt;--driver&lt;/span&gt; overlay myoverlay

&lt;span class="c"&gt;# Attachable overlay (standalone containers can join too)&lt;/span&gt;
docker network create &lt;span class="nt"&gt;--driver&lt;/span&gt; overlay &lt;span class="nt"&gt;--attachable&lt;/span&gt; myoverlay

&lt;span class="c"&gt;# Uses VXLAN tunneling automatically&lt;/span&gt;
&lt;span class="c"&gt;# Containers are unaware they are on different hosts!&lt;/span&gt;
&lt;span class="c"&gt;# 10.0.0.2 (Server 1) &amp;lt;-&amp;gt; 10.0.0.3 (Server 2) — transparent!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Overlay = VXLAN Tunneling.&lt;/strong&gt; Inside an overlay network, Docker uses &lt;strong&gt;VXLAN (Virtual Extensible LAN)&lt;/strong&gt; — one packet is wrapped inside another. A packet from a container on Server A is wrapped in UDP and sent to Server B, where it is unwrapped. Containers have no idea they are running on different machines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Macvlan — Container as a Physical Device
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container gets its own MAC address&lt;/span&gt;
&lt;span class="c"&gt;# Appears on the network like a real physical device&lt;/span&gt;

docker network create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; macvlan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="se"&gt;\&lt;/span&gt;
  mymacvlan

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; legacy-app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; mymacvlan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ip&lt;/span&gt; 192.168.1.50 &lt;span class="se"&gt;\&lt;/span&gt;
  my-legacy-app

&lt;span class="c"&gt;# The router treats 192.168.1.50 as a physical machine!&lt;/span&gt;

&lt;span class="c"&gt;# Use cases:&lt;/span&gt;
&lt;span class="c"&gt;# - Legacy applications that expect direct network access&lt;/span&gt;
&lt;span class="c"&gt;# - Migrating VMs to containers&lt;/span&gt;
&lt;span class="c"&gt;# - Network monitoring tools&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Bridge vs Host Network.&lt;/strong&gt; &lt;strong&gt;Bridge:&lt;/strong&gt; Container has its own network namespace, communicates with the host via NAT, and requires port mapping. &lt;strong&gt;Host:&lt;/strong&gt; Container shares the host's network namespace — no NAT, no port mapping, maximum performance. Use Bridge when isolation is required; use Host when performance is critical and isolation can be sacrificed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Always Use User-Defined Bridges.&lt;/strong&gt; Never use the default bridge network in production. Create a dedicated user-defined network for each application stack. This provides DNS resolution, isolation, and security all in one. Docker Compose automatically creates a user-defined network for each project.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Network Commands — Complete Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  docker network ls — Listing Networks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all networks&lt;/span&gt;
docker network &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="c"&gt;# NETWORK ID     NAME      DRIVER    SCOPE&lt;/span&gt;
&lt;span class="c"&gt;# a1b2c3d4e5f6   bridge    bridge    local&lt;/span&gt;
&lt;span class="c"&gt;# f6e5d4c3b2a1   host      host      local&lt;/span&gt;
&lt;span class="c"&gt;# 0000000000000   none      null      local&lt;/span&gt;

&lt;span class="c"&gt;# Show full IDs&lt;/span&gt;
docker network &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--no-trunc&lt;/span&gt;

&lt;span class="c"&gt;# Filter by driver&lt;/span&gt;
docker network &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bridge

&lt;span class="c"&gt;# Filter by name&lt;/span&gt;
docker network &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mynetwork

&lt;span class="c"&gt;# Custom format&lt;/span&gt;
docker network &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.ID}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Name}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Driver}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Scope}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker network create — Creating a Network
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Simple bridge network&lt;/span&gt;
docker network create mynetwork

&lt;span class="c"&gt;# Bridge with full options&lt;/span&gt;
docker network create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; bridge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet&lt;/span&gt; 172.20.0.0/16 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ip-range&lt;/span&gt; 172.20.240.0/20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gateway&lt;/span&gt; 172.20.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="se"&gt;\&lt;/span&gt;
  mynetwork

&lt;span class="c"&gt;# Internal network (no external access)&lt;/span&gt;
&lt;span class="c"&gt;# Containers cannot connect to the internet&lt;/span&gt;
docker network create &lt;span class="nt"&gt;--internal&lt;/span&gt; private-db-net

&lt;span class="c"&gt;# Overlay network (in Swarm mode)&lt;/span&gt;
docker network create &lt;span class="nt"&gt;--driver&lt;/span&gt; overlay &lt;span class="nt"&gt;--attachable&lt;/span&gt; myoverlay

&lt;span class="c"&gt;# Macvlan network&lt;/span&gt;
docker network create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; macvlan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gateway&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="se"&gt;\&lt;/span&gt;
  mymacvlan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker network inspect — Viewing Details
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full JSON details&lt;/span&gt;
docker network inspect mynetwork

&lt;span class="c"&gt;# Specific field — IPAM config&lt;/span&gt;
docker network inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.IPAM.Config}}'&lt;/span&gt; mynetwork

&lt;span class="c"&gt;# Find the gateway&lt;/span&gt;
docker network inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{range .IPAM.Config}}{{.Gateway}}{{end}}'&lt;/span&gt; mynetwork

&lt;span class="c"&gt;# List connected containers (requires jq)&lt;/span&gt;
docker network inspect mynetwork | jq &lt;span class="s1"&gt;'.[0].Containers'&lt;/span&gt;

&lt;span class="c"&gt;# Most useful: which containers are on this network&lt;/span&gt;
docker network inspect mynetwork &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{"\n"}}{{end}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker network connect / disconnect — Runtime Changes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Connect a running container to a new network&lt;/span&gt;
docker network connect mynetwork mycontainer

&lt;span class="c"&gt;# Assign a specific IP&lt;/span&gt;
docker network connect &lt;span class="nt"&gt;--ip&lt;/span&gt; 172.20.0.100 mynetwork mycontainer

&lt;span class="c"&gt;# Add an alias (other containers can reach this container via the alias)&lt;/span&gt;
docker network connect &lt;span class="nt"&gt;--alias&lt;/span&gt; db-primary mynetwork mycontainer

&lt;span class="c"&gt;# Disconnect (container keeps running)&lt;/span&gt;
docker network disconnect mynetwork mycontainer

&lt;span class="c"&gt;# Force disconnect&lt;/span&gt;
docker network disconnect &lt;span class="nt"&gt;--force&lt;/span&gt; mynetwork mycontainer

&lt;span class="c"&gt;# Real use case: zero-downtime maintenance&lt;/span&gt;
&lt;span class="c"&gt;# 1. Start new container on the new network&lt;/span&gt;
&lt;span class="c"&gt;# 2. Gradually migrate old containers&lt;/span&gt;
&lt;span class="c"&gt;# 3. Disconnect from the old network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Multiple Networks — One Container on Many Networks!&lt;/strong&gt; A container can be connected to more than one network simultaneously. For example, a "gateway" container can be connected to the frontend network on one side and the backend network on the other. This is useful in DMZ architectures where you want to control traffic flow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker network rm / prune — Cleanup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove a network (disconnect all containers first!)&lt;/span&gt;
docker network &lt;span class="nb"&gt;rm &lt;/span&gt;mynetwork
&lt;span class="c"&gt;# Error: network mynetwork has active endpoints — if containers are attached&lt;/span&gt;

&lt;span class="c"&gt;# Remove containers first, then the network&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mynetwork&lt;span class="si"&gt;)&lt;/span&gt;
docker network &lt;span class="nb"&gt;rm &lt;/span&gt;mynetwork

&lt;span class="c"&gt;# Remove multiple networks at once&lt;/span&gt;
docker network &lt;span class="nb"&gt;rm &lt;/span&gt;net1 net2 net3

&lt;span class="c"&gt;# Remove all unused networks (none with connected containers)&lt;/span&gt;
docker network prune

&lt;span class="c"&gt;# Skip confirmation prompt&lt;/span&gt;
docker network prune &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Filter by label before pruning&lt;/span&gt;
docker network prune &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;docker network prune — Be Careful!&lt;/strong&gt; This command deletes all "unused" networks — meaning any network not connected to at least one container. If you created a network for future use, it will be deleted too. Always run &lt;code&gt;docker network ls&lt;/code&gt; before running &lt;code&gt;prune&lt;/code&gt; in production.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Container-to-Container Communication
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Real-World Example: MERN Stack Communication
&lt;/h3&gt;

&lt;p&gt;Consider deploying a MERN (MongoDB, Express, React, Node) application. All three services need to communicate with each other. Here is how to set this up in Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Create a dedicated network&lt;/span&gt;
docker network create mern-network

&lt;span class="c"&gt;# Step 2: Start MongoDB first (others depend on it)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; mongodb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; mern-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; mongo_data:/data/db &lt;span class="se"&gt;\&lt;/span&gt;
  mongo:7

&lt;span class="c"&gt;# Step 3: Start the Node.js API&lt;/span&gt;
&lt;span class="c"&gt;# Use "mongodb" as the hostname — Docker DNS resolves it!&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; mern-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGODB_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb://mongodb:27017/mydb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 &lt;span class="se"&gt;\&lt;/span&gt;
  myapi:latest

&lt;span class="c"&gt;# Step 4: Start the React frontend&lt;/span&gt;
&lt;span class="c"&gt;# Use "api" as the hostname&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; frontend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; mern-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;REACT_APP_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://api:5000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  myfrontend:latest

&lt;span class="c"&gt;# Communication flow:&lt;/span&gt;
&lt;span class="c"&gt;# Browser → Host:3000 → frontend container&lt;/span&gt;
&lt;span class="c"&gt;# frontend → api:5000 (DNS resolution via Docker) → api container&lt;/span&gt;
&lt;span class="c"&gt;# api → mongodb:27017 (DNS resolution via Docker) → mongodb container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing DNS Resolution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test connectivity&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;frontend ping api &lt;span class="nt"&gt;-c&lt;/span&gt; 4
&lt;span class="c"&gt;# PING api (172.20.0.3): 56 data bytes&lt;/span&gt;
&lt;span class="c"&gt;# 64 bytes from 172.20.0.3: icmp_seq=0 ttl=64 time=0.089 ms&lt;/span&gt;

&lt;span class="c"&gt;# DNS lookup&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;api nslookup mongodb
&lt;span class="c"&gt;# Server:    127.0.0.11  &amp;lt;- Docker's built-in DNS server!&lt;/span&gt;
&lt;span class="c"&gt;# Address 1: 127.0.0.11&lt;/span&gt;
&lt;span class="c"&gt;# Name:      mongodb&lt;/span&gt;
&lt;span class="c"&gt;# Address 1: 172.20.0.2 mongodb.mern-network&lt;/span&gt;

&lt;span class="c"&gt;# Test HTTP connection&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;api wget &lt;span class="nt"&gt;-qO-&lt;/span&gt; http://mongodb:27017
&lt;span class="c"&gt;# It looks like you are trying to access MongoDB over HTTP...&lt;/span&gt;
&lt;span class="c"&gt;# (Connection succeeded — MongoDB responded)&lt;/span&gt;

&lt;span class="c"&gt;# Test the API from the frontend using curl&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;frontend curl http://api:5000/health
&lt;span class="c"&gt;# {"status":"ok","db":"connected"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;127.0.0.11 — Docker's Secret DNS Server.&lt;/strong&gt; Whenever you use a user-defined network, Docker provides an embedded DNS server at &lt;strong&gt;127.0.0.11&lt;/strong&gt; inside every container. It resolves container names to IP addresses. This happens automatically — no configuration required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Service Discovery Patterns — Best Practices
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;When to Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Container Name as Hostname&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mongodb://mongodb:27017&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simple setups, same host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Compose Service Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://api:5000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compose-based deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Alias&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--network-alias db&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the container name may change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment Variables&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DB_HOST=mongodb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Externalizing configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;Never Hardcode IPs!&lt;/strong&gt; A very common mistake: &lt;code&gt;MONGODB_URI=mongodb://172.17.0.2:27017&lt;/code&gt;. The IP can change when the container restarts. Always use the container name as the hostname: &lt;code&gt;mongodb://mongodb:27017&lt;/code&gt;. The container name is stable; the IP is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Production Pattern: Network Segmentation.&lt;/strong&gt; In real production environments, create separate networks: &lt;code&gt;frontend-net&lt;/code&gt; (frontend + API) and &lt;code&gt;backend-net&lt;/code&gt; (API + database). The database will not be directly accessible from the frontend — only through the API. This is the Zero Trust networking principle. In Docker Compose, this is easily configured with the &lt;code&gt;networks:&lt;/code&gt; key.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Port Management — EXPOSE vs -p vs -P
&lt;/h2&gt;

&lt;h3&gt;
  
  
  EXPOSE vs -p — Clearing the Confusion
&lt;/h3&gt;

&lt;p&gt;This is a classic point of confusion for beginners. Both &lt;code&gt;EXPOSE&lt;/code&gt; and &lt;code&gt;-p&lt;/code&gt; deal with ports, but they serve different purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# EXPOSE in Dockerfile — documentation only!&lt;/span&gt;
EXPOSE 3000
&lt;span class="c"&gt;# This only says "this container listens on port 3000"&lt;/span&gt;
&lt;span class="c"&gt;# It does NOT publish anything to the HOST&lt;/span&gt;
&lt;span class="c"&gt;# Other containers can read this information via inspect&lt;/span&gt;

&lt;span class="c"&gt;# -p flag: ACTUALLY publishes the port on the host&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:3000 myapp
&lt;span class="c"&gt;# host:8080 → container:3000&lt;/span&gt;
&lt;span class="c"&gt;# Requests arriving at host port 8080 are forwarded to container port 3000&lt;/span&gt;

&lt;span class="c"&gt;# -P flag (capital P): publish ALL EXPOSEd ports to random host ports&lt;/span&gt;
docker run &lt;span class="nt"&gt;-P&lt;/span&gt; myapp
&lt;span class="c"&gt;# If Dockerfile has EXPOSE 3000 3306:&lt;/span&gt;
&lt;span class="c"&gt;# Docker allocates: 32768:3000, 32769:3306 (random!)&lt;/span&gt;

&lt;span class="c"&gt;# Check which port was assigned&lt;/span&gt;
docker port myapp
&lt;span class="c"&gt;# 3000/tcp -&amp;gt; 0.0.0.0:32768&lt;/span&gt;

docker port myapp 3000
&lt;span class="c"&gt;# 0.0.0.0:32768&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Mnemonic: EXPOSE = Advertisement, -p = Actually Open the Door.&lt;/strong&gt; EXPOSE is a "menu card" — it only describes what is available. The &lt;code&gt;-p&lt;/code&gt; flag actually opens the door to the outside world. Without &lt;code&gt;-p&lt;/code&gt;, services inside the container are not accessible from the host (though other containers on a custom network can still access them without port mapping).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Port Mapping Patterns
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Specific mapping (most common)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx          &lt;span class="c"&gt;# Host 8080 → Container 80&lt;/span&gt;

&lt;span class="c"&gt;# 2. Multiple ports&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 &lt;span class="nt"&gt;-p&lt;/span&gt; 443:443 nginx

&lt;span class="c"&gt;# 3. Default binding: 0.0.0.0 (all interfaces)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx
&lt;span class="c"&gt;# Any IP can reach port 8080 and hit the container&lt;/span&gt;
&lt;span class="c"&gt;# curl localhost:8080 works&lt;/span&gt;
&lt;span class="c"&gt;# curl 192.168.1.100:8080 works (accessible on LAN too)&lt;/span&gt;

&lt;span class="c"&gt;# 4. Bind to localhost only (security)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:8080:80 nginx
&lt;span class="c"&gt;# Only accessible from the same machine&lt;/span&gt;
&lt;span class="c"&gt;# curl localhost:8080 works&lt;/span&gt;
&lt;span class="c"&gt;# curl 192.168.1.100:8080 does not work&lt;/span&gt;

&lt;span class="c"&gt;# 5. Random port on all interfaces&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 0.0.0.0::80 nginx
&lt;span class="c"&gt;# Docker chooses a random high port&lt;/span&gt;
docker port &amp;lt;container&amp;gt; 80
&lt;span class="c"&gt;# 0.0.0.0:49153&lt;/span&gt;

&lt;span class="c"&gt;# 6. UDP port&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 53:53/udp my-dns-server

&lt;span class="c"&gt;# 7. Range of ports&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8000-8010:8000-8010 myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling Port Conflicts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Error: Bind: address already in use&lt;/span&gt;
&lt;span class="c"&gt;# docker: Error response from daemon: driver failed programming&lt;/span&gt;
&lt;span class="c"&gt;# external connectivity on endpoint ...: Bind for 0.0.0.0:8080&lt;/span&gt;
&lt;span class="c"&gt;# failed: port is already allocated&lt;/span&gt;

&lt;span class="c"&gt;# Step 1: Find out who is using port 8080&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;lsof &lt;span class="nt"&gt;-i&lt;/span&gt; :8080
&lt;span class="nb"&gt;sudo &lt;/span&gt;netstat &lt;span class="nt"&gt;-tulpn&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;8080

&lt;span class="c"&gt;# Step 2: Options:&lt;/span&gt;
&lt;span class="c"&gt;# Option A: Use a different host port&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 9090:80 nginx

&lt;span class="c"&gt;# Option B: Stop the conflicting process&lt;/span&gt;
&lt;span class="nb"&gt;sudo kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; &amp;lt;PID&amp;gt;

&lt;span class="c"&gt;# Option C: Check if another Docker container is using the port&lt;/span&gt;
docker ps &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.Names}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Ports}}"&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;8080
docker stop old-nginx
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx

&lt;span class="c"&gt;# Useful: view all mapped ports&lt;/span&gt;
docker ps &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.ID}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Names}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Ports}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;Security: Bind to Localhost in Development.&lt;/strong&gt; When you use &lt;code&gt;-p 8080:80&lt;/code&gt;, Docker automatically adds an iptables rule — even if UFW or firewalld is disabled. This means the service may be accessible from the internet. In development, always use &lt;code&gt;-p 127.0.0.1:8080:80&lt;/code&gt; for sensitive services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;EXPOSE vs -p — Interview Answer.&lt;/strong&gt; &lt;code&gt;EXPOSE&lt;/code&gt; is only metadata/documentation — it does not actually publish any port. The &lt;code&gt;-p&lt;/code&gt; flag creates an actual port mapping at runtime between the host and container using iptables rules. EXPOSE without &lt;code&gt;-p&lt;/code&gt; is useless for host access (though it does provide information to other containers within the network).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Storage Fundamentals — Why Persistence Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Containers Are Ephemeral — Data Is Lost!
&lt;/h3&gt;

&lt;p&gt;The most important concept in storage: &lt;strong&gt;containers are ephemeral (temporary) by default&lt;/strong&gt;. When a container is deleted, all its data is deleted too.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;"The container was deleted and the data went with it!" — Classic Production Disaster.&lt;/strong&gt; Imagine you started a MySQL container, added some data, and then accidentally ran &lt;code&gt;docker rm -f mysql&lt;/code&gt;. The entire database is gone. This happens frequently in production. Solution: always use volumes for databases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Container storage layers use a union filesystem (overlay2):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────┐
│  [Writable Layer]  ← Temporary!         │
│  The container's write area             │
│  Container deleted → THIS DATA IS GONE! │
├─────────────────────────────────────────┤
│  [App Layer]       ← Read-only          │
│  Your application code                  │
├─────────────────────────────────────────┤
│  [Base Image Layer] ← Read-only         │
│  ubuntu:22.04 or node:18 etc.           │
└─────────────────────────────────────────┘

# The bottom layers are part of the image — shared across containers!
# Only the writable layer is container-specific — and it is temporary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Storage Drivers — The Underlying Filesystem
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Driver&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;When to Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;overlay2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Default; uses Linux overlayfs&lt;/td&gt;
&lt;td&gt;Almost always — best choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;devicemapper&lt;/td&gt;
&lt;td&gt;Block device based&lt;/td&gt;
&lt;td&gt;Legacy RHEL/CentOS (outdated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;btrfs&lt;/td&gt;
&lt;td&gt;Btrfs filesystem&lt;/td&gt;
&lt;td&gt;Only on Btrfs hosts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zfs&lt;/td&gt;
&lt;td&gt;ZFS filesystem&lt;/td&gt;
&lt;td&gt;Only on ZFS hosts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vfs&lt;/td&gt;
&lt;td&gt;No layering, simple copy&lt;/td&gt;
&lt;td&gt;Testing only — very slow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check the current storage driver&lt;/span&gt;
docker info | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Storage Driver"&lt;/span&gt;
&lt;span class="c"&gt;# Storage Driver: overlay2&lt;/span&gt;

&lt;span class="c"&gt;# Where does Docker store data?&lt;/span&gt;
/var/lib/docker/
  ├── containers/     &lt;span class="c"&gt;# Per-container metadata, logs&lt;/span&gt;
  ├── image/          &lt;span class="c"&gt;# Image metadata and manifests&lt;/span&gt;
  ├── overlay2/       &lt;span class="c"&gt;# Image layers (most data is here!)&lt;/span&gt;
  ├── volumes/        &lt;span class="c"&gt;# Named volumes&lt;/span&gt;
  └── network/        &lt;span class="c"&gt;# Network configs&lt;/span&gt;

&lt;span class="c"&gt;# Check sizes&lt;/span&gt;
&lt;span class="nb"&gt;sudo du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; /var/lib/docker/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# 1.2G    /var/lib/docker/overlay2   # Most data here!&lt;/span&gt;
&lt;span class="c"&gt;# 200M    /var/lib/docker/volumes&lt;/span&gt;
&lt;span class="c"&gt;# 50M     /var/lib/docker/containers&lt;/span&gt;

&lt;span class="c"&gt;# Docker summary&lt;/span&gt;
docker system &lt;span class="nb"&gt;df&lt;/span&gt;
&lt;span class="c"&gt;# TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE&lt;/span&gt;
&lt;span class="c"&gt;# Images          15        5         3.2GB     2.1GB (65%)&lt;/span&gt;
&lt;span class="c"&gt;# Containers      8         3         120MB     90MB (75%)&lt;/span&gt;
&lt;span class="c"&gt;# Local Volumes   5         2         1.1GB     500MB (45%)&lt;/span&gt;

&lt;span class="c"&gt;# Detailed breakdown&lt;/span&gt;
docker system &lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Copy-on-Write (CoW) — The Secret of Efficiency.&lt;/strong&gt; overlay2 uses a &lt;strong&gt;Copy-on-Write&lt;/strong&gt; strategy. If 10 containers use the same base image, the image layers are stored only once on disk. Only when a container writes something does it create its own copy of that file. This saves both disk space and startup time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Production Rule: Databases Always Need Volumes!&lt;/strong&gt; MySQL, PostgreSQL, MongoDB, Redis — whenever you run any database in a container, volumes are MANDATORY. Without a volume, all data is permanently lost when the container crashes or restarts. This is the number one cause of production disasters.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Volume Types — Named, Bind Mount, tmpfs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Named Volumes — Production's Best Friend
&lt;/h3&gt;

&lt;p&gt;Named volumes are Docker's &lt;strong&gt;managed volumes&lt;/strong&gt; — Docker decides where to store the data (&lt;code&gt;/var/lib/docker/volumes/&lt;/code&gt;). They survive container restarts and deletions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a volume&lt;/span&gt;
docker volume create mydata

&lt;span class="c"&gt;# Use with -v syntax&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; mydata:/app/data myapp

&lt;span class="c"&gt;# Use with --mount syntax (recommended)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;volume,source&lt;span class="o"&gt;=&lt;/span&gt;mydata,target&lt;span class="o"&gt;=&lt;/span&gt;/app/data myapp

&lt;span class="c"&gt;# Use with a database&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; postgres_data:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
  postgres:16

&lt;span class="c"&gt;# Delete the container — THE VOLUME STAYS SAFE!&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; postgres
docker volume &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="c"&gt;# postgres_data  ← Still there!&lt;/span&gt;

&lt;span class="c"&gt;# Start a new container with the same volume — data is back!&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; postgres-new &lt;span class="nt"&gt;-v&lt;/span&gt; postgres_data:/var/lib/postgresql/data postgres:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bind Mounts — Development's Superpower
&lt;/h3&gt;

&lt;p&gt;With bind mounts, you specify the host path explicitly. This is extremely useful in development because your local changes are instantly reflected inside the container — &lt;strong&gt;without rebuilding&lt;/strong&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Specify the host path explicitly&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /home/pushkar/myproject:/app node:18

&lt;span class="c"&gt;# Mount the current directory (most common in development!)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app node:18
&lt;span class="c"&gt;# Edit code locally — changes appear inside the container immediately!&lt;/span&gt;
&lt;span class="c"&gt;# Hot reload works!&lt;/span&gt;

&lt;span class="c"&gt;# Node.js development example&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; dev-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; /app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  node:18 &lt;span class="se"&gt;\&lt;/span&gt;
  npm run dev

&lt;span class="c"&gt;# Read-only bind mount (for security — container cannot modify)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /etc/nginx/conf:/etc/nginx/conf:ro nginx

&lt;span class="c"&gt;# --mount syntax&lt;/span&gt;
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;,target&lt;span class="o"&gt;=&lt;/span&gt;/app node:18
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;/config,target&lt;span class="o"&gt;=&lt;/span&gt;/config,readonly nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;For React Developers: Bind Mount = Live Reload!&lt;/strong&gt; &lt;code&gt;docker run -v $(pwd):/app -p 3000:3000 node:18 npm start&lt;/code&gt; — edit code in VS Code and React hot reload fires automatically inside the container. No rebuild needed for each change. This is a game-changer for development workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  tmpfs Mounts — Sensitive Data In-Memory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stored in memory, never written to disk!&lt;/span&gt;
docker run &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp nginx

&lt;span class="c"&gt;# With a size limit&lt;/span&gt;
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tmpfs,target&lt;span class="o"&gt;=&lt;/span&gt;/tmp,tmpfs-size&lt;span class="o"&gt;=&lt;/span&gt;100m myapp

&lt;span class="c"&gt;# Multiple tmpfs locations&lt;/span&gt;
docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /run &lt;span class="se"&gt;\&lt;/span&gt;
  myapp

&lt;span class="c"&gt;# Use cases:&lt;/span&gt;
&lt;span class="c"&gt;# - Session data (passwords, tokens stored temporarily)&lt;/span&gt;
&lt;span class="c"&gt;# - Sensitive computation results&lt;/span&gt;
&lt;span class="c"&gt;# - Cache data that should never touch disk&lt;/span&gt;
&lt;span class="c"&gt;# - High-speed temporary storage&lt;/span&gt;

&lt;span class="c"&gt;# Container stops → data gone immediately (from RAM)&lt;/span&gt;
&lt;span class="c"&gt;# Container crashes → data gone immediately&lt;/span&gt;
&lt;span class="c"&gt;# Never persists to disk = more secure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comparison — Which Type to Use When?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Named Volume&lt;/th&gt;
&lt;th&gt;Bind Mount&lt;/th&gt;
&lt;th&gt;tmpfs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Storage Location&lt;/td&gt;
&lt;td&gt;/var/lib/docker/volumes&lt;/td&gt;
&lt;td&gt;Any host path&lt;/td&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Managed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Persists&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (memory only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev Workflow&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;Best (hot reload!)&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production DB&lt;/td&gt;
&lt;td&gt;Best&lt;/td&gt;
&lt;td&gt;OK (path management)&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Easy Backup&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Best (NFS will be slow)&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;Container isolated&lt;/td&gt;
&lt;td&gt;Host exposed&lt;/td&gt;
&lt;td&gt;Most secure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Named Volume vs Bind Mount — Interview Answer.&lt;/strong&gt; &lt;strong&gt;Named Volume:&lt;/strong&gt; Docker manages the location; it is portable, easy to back up, and best for production. &lt;strong&gt;Bind Mount:&lt;/strong&gt; You control the specific host path; perfect for development (host files accessible directly in container), but path is host-specific — less portable. Prefer named volumes in production; use bind mounts in development for code changes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Volume Commands — Complete Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  docker volume create — Creating Volumes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Simple named volume&lt;/span&gt;
docker volume create mydb-data

&lt;span class="c"&gt;# Explicitly use the local driver (this is already the default)&lt;/span&gt;
docker volume create &lt;span class="nt"&gt;--driver&lt;/span&gt; &lt;span class="nb"&gt;local &lt;/span&gt;mydb-data

&lt;span class="c"&gt;# NFS-backed volume (remote storage!)&lt;/span&gt;
docker volume create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nfs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nv"&gt;o&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.100,rw,vers&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nv"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;:/exports/mydata &lt;span class="se"&gt;\&lt;/span&gt;
  nfs-volume

&lt;span class="c"&gt;# With labels (for management)&lt;/span&gt;
docker volume create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--label&lt;/span&gt; &lt;span class="nv"&gt;team&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;backend &lt;span class="se"&gt;\&lt;/span&gt;
  mydb-data

&lt;span class="c"&gt;# Volumes are automatically created if referenced in a run command&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; newvolume:/data myapp
&lt;span class="c"&gt;# "newvolume" is created automatically if it does not exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker volume ls, inspect, rm, prune
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all volumes&lt;/span&gt;
docker volume &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="c"&gt;# DRIVER    VOLUME NAME&lt;/span&gt;
&lt;span class="c"&gt;# local     mydb-data&lt;/span&gt;
&lt;span class="c"&gt;# local     postgres_data&lt;/span&gt;
&lt;span class="c"&gt;# local     mongo_data&lt;/span&gt;

&lt;span class="c"&gt;# Find dangling volumes (not connected to any container)&lt;/span&gt;
docker volume &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;dangling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Filter by label&lt;/span&gt;
docker volume &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production

&lt;span class="c"&gt;# Inspect — full details&lt;/span&gt;
docker volume inspect mydb-data
&lt;span class="c"&gt;# [&lt;/span&gt;
&lt;span class="c"&gt;#   {&lt;/span&gt;
&lt;span class="c"&gt;#     "Name": "mydb-data",&lt;/span&gt;
&lt;span class="c"&gt;#     "Driver": "local",&lt;/span&gt;
&lt;span class="c"&gt;#     "Mountpoint": "/var/lib/docker/volumes/mydb-data/_data",&lt;/span&gt;
&lt;span class="c"&gt;#     "Labels": {"env": "production"},&lt;/span&gt;
&lt;span class="c"&gt;#     ...&lt;/span&gt;
&lt;span class="c"&gt;#   }&lt;/span&gt;
&lt;span class="c"&gt;# ]&lt;/span&gt;

&lt;span class="c"&gt;# Get only the mount path&lt;/span&gt;
docker volume inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Mountpoint}}'&lt;/span&gt; mydb-data
&lt;span class="c"&gt;# /var/lib/docker/volumes/mydb-data/_data&lt;/span&gt;

&lt;span class="c"&gt;# Remove — stop the container first!&lt;/span&gt;
docker volume &lt;span class="nb"&gt;rm &lt;/span&gt;mydb-data
&lt;span class="c"&gt;# Error: volume is in use — if a container is running&lt;/span&gt;

&lt;span class="c"&gt;# Correct approach: remove the container first&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; mycontainer
docker volume &lt;span class="nb"&gt;rm &lt;/span&gt;mydb-data

&lt;span class="c"&gt;# Remove multiple at once&lt;/span&gt;
&lt;span class="c"&gt;# (Remove containers first, then volumes)&lt;/span&gt;
docker volume prune

&lt;span class="c"&gt;# Skip confirmation&lt;/span&gt;
docker volume prune &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Filter by label before pruning&lt;/span&gt;
docker volume prune &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  --mount vs -v Syntax — The Modern Way
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ─── OLD -v SYNTAX ──────────────────────────────────────&lt;/span&gt;
&lt;span class="c"&gt;# Older style, still works, but less readable&lt;/span&gt;

&lt;span class="c"&gt;# Named volume&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; myvolume:/app/data myapp

&lt;span class="c"&gt;# Bind mount&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /host/path:/container/path myapp

&lt;span class="c"&gt;# Read-only bind mount&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /host/path:/container/path:ro myapp

&lt;span class="c"&gt;# ─── NEW --mount SYNTAX ──────────────────────────────────&lt;/span&gt;
&lt;span class="c"&gt;# Newer, explicit, recommended&lt;/span&gt;

&lt;span class="c"&gt;# Named volume&lt;/span&gt;
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;volume,source&lt;span class="o"&gt;=&lt;/span&gt;myvolume,target&lt;span class="o"&gt;=&lt;/span&gt;/app/data myapp

&lt;span class="c"&gt;# Bind mount&lt;/span&gt;
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;/host/path,target&lt;span class="o"&gt;=&lt;/span&gt;/app/path myapp

&lt;span class="c"&gt;# Read-only bind mount&lt;/span&gt;
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;/host/path,target&lt;span class="o"&gt;=&lt;/span&gt;/app/path,readonly myapp

&lt;span class="c"&gt;# tmpfs&lt;/span&gt;
docker run &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tmpfs,target&lt;span class="o"&gt;=&lt;/span&gt;/tmp,tmpfs-size&lt;span class="o"&gt;=&lt;/span&gt;100m myapp

&lt;span class="c"&gt;# Why --mount is better:&lt;/span&gt;
&lt;span class="c"&gt;# - More explicit — type is clearly stated&lt;/span&gt;
&lt;span class="c"&gt;# - Better error messages&lt;/span&gt;
&lt;span class="c"&gt;# - Supports all options (tmpfs-size, etc.)&lt;/span&gt;
&lt;span class="c"&gt;# - Recommended format in Docker Docs&lt;/span&gt;
&lt;span class="c"&gt;# - More readable in scripts&lt;/span&gt;
&lt;span class="c"&gt;# - Named parameters catch typos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;-v vs --mount: One Important Difference.&lt;/strong&gt; A subtle distinction: &lt;code&gt;-v /nonexistent:/data&lt;/code&gt; will cause Docker to automatically create the host directory (sometimes with the wrong type). &lt;code&gt;--mount type=bind,source=/nonexistent,target=/data&lt;/code&gt; returns an error if the source does not exist. &lt;strong&gt;--mount is stricter and more predictable&lt;/strong&gt; — which makes it better for production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;docker volume prune — Be Very Careful in Production!&lt;/strong&gt; This command deletes "dangling" (unused) volumes. However, if you have temporarily stopped a container (intending to restore its data), its volume may be deleted too. In production, first run &lt;code&gt;docker volume ls -f dangling=true&lt;/code&gt; to see which volumes are actually unused, then selectively use &lt;code&gt;docker volume rm&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Production Volume Patterns — Real-World Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Database Persistence Patterns
&lt;/h3&gt;

&lt;h4&gt;
  
  
  PostgreSQL with Named Volume
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Create the volume&lt;/span&gt;
docker volume create postgres_data

&lt;span class="c"&gt;# Step 2: Run PostgreSQL with a proper production setup&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;securepassword123 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; postgres_data:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:5432:5432 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pg_isready -U admin"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10s &lt;span class="se"&gt;\&lt;/span&gt;
  postgres:16-alpine

&lt;span class="c"&gt;# Verify&lt;/span&gt;
docker ps                                         &lt;span class="c"&gt;# Check status and health&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;postgres psql &lt;span class="nt"&gt;-U&lt;/span&gt; admin &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\l&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;        &lt;span class="c"&gt;# List databases&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  MongoDB with Multiple Volumes
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# MongoDB has two important paths:&lt;/span&gt;
&lt;span class="c"&gt;# /data/db       — actual database files&lt;/span&gt;
&lt;span class="c"&gt;# /data/configdb — configuration data&lt;/span&gt;

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; mongodb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; mongo_data:/data/db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; mongo_config:/data/configdb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;securepass &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:27017:27017 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  mongo:7

&lt;span class="c"&gt;# Verify&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;mongodb mongosh &lt;span class="nt"&gt;-u&lt;/span&gt; admin &lt;span class="nt"&gt;-p&lt;/span&gt; securepass &lt;span class="nt"&gt;--eval&lt;/span&gt; &lt;span class="s2"&gt;"db.adminCommand('listDatabases')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backup Strategies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ─── VOLUME BACKUP (any type of data) ─────────────────────&lt;/span&gt;
&lt;span class="c"&gt;# Use a temporary container — read the volume, create a tar archive&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; mydb_data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/backups:/backup &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;tar &lt;/span&gt;czf /backup/mydb-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d-%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;.tar.gz /data

&lt;span class="c"&gt;# Creates a file in /backup: mydb-20240115-142030.tar.gz&lt;/span&gt;

&lt;span class="c"&gt;# ─── VOLUME RESTORE ────────────────────────────────────────&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; mydb_data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/backups:/backup &lt;span class="se"&gt;\&lt;/span&gt;
  alpine &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;tar &lt;/span&gt;xzf /backup/mydb-20240115-142030.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /

&lt;span class="c"&gt;# ─── POSTGRES DUMP (recommended for PostgreSQL) ────────────&lt;/span&gt;
&lt;span class="c"&gt;# Logical backup — portable and version-aware&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;postgres pg_dump &lt;span class="nt"&gt;-U&lt;/span&gt; admin myapp &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; backup-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;.sql

&lt;span class="c"&gt;# Restore&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; postgres psql &lt;span class="nt"&gt;-U&lt;/span&gt; admin myapp &amp;lt; backup-20240115.sql

&lt;span class="c"&gt;# Compressed backup&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;postgres pg_dump &lt;span class="nt"&gt;-U&lt;/span&gt; admin myapp | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; backup.sql.gz

&lt;span class="c"&gt;# Restore compressed&lt;/span&gt;
&lt;span class="nb"&gt;gunzip&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; backup.sql.gz | docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; postgres psql &lt;span class="nt"&gt;-U&lt;/span&gt; admin myapp

&lt;span class="c"&gt;# ─── MONGODB DUMP ──────────────────────────────────────────&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;mongodb mongodump &lt;span class="nt"&gt;-u&lt;/span&gt; admin &lt;span class="nt"&gt;-p&lt;/span&gt; securepass &lt;span class="nt"&gt;--out&lt;/span&gt; /tmp/backup
docker &lt;span class="nb"&gt;cp &lt;/span&gt;mongodb:/tmp/backup ./mongodb-backup

&lt;span class="c"&gt;# Restore&lt;/span&gt;
docker &lt;span class="nb"&gt;cp&lt;/span&gt; ./mongodb-backup mongodb:/tmp/backup
docker &lt;span class="nb"&gt;exec &lt;/span&gt;mongodb mongorestore &lt;span class="nt"&gt;-u&lt;/span&gt; admin &lt;span class="nt"&gt;-p&lt;/span&gt; securepass /tmp/backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shared Volumes Between Containers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pattern: one container writes, another reads&lt;/span&gt;
&lt;span class="c"&gt;# No network communication required between them!&lt;/span&gt;
docker volume create shared-logs

&lt;span class="c"&gt;# App container — writes logs&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; app1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; shared-logs:/var/log/app &lt;span class="se"&gt;\&lt;/span&gt;
  myapp:latest

&lt;span class="c"&gt;# Log viewer — read-only access to the same volume&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; log-viewer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; shared-logs:/logs:ro &lt;span class="se"&gt;\&lt;/span&gt;
  log-viewer-app:latest

&lt;span class="c"&gt;# log-viewer can now read all of app1's logs&lt;/span&gt;
&lt;span class="c"&gt;# No network communication needed!&lt;/span&gt;

&lt;span class="c"&gt;# Real use case: Nginx serving React static files&lt;/span&gt;
docker volume create static-files

&lt;span class="c"&gt;# Build container: build React, copy to volume&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; static-files:/app/build &lt;span class="se"&gt;\&lt;/span&gt;
  react-builder:latest

&lt;span class="c"&gt;# Nginx container: serve files from the same volume&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; nginx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; static-files:/usr/share/nginx/html:ro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 &lt;span class="se"&gt;\&lt;/span&gt;
  nginx:alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cloud Volume Drivers — AWS EBS, Azure Disk
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AWS EBS-backed volumes (rexray plugin)&lt;/span&gt;
&lt;span class="c"&gt;# If the host is replaced, data remains safe on AWS EBS!&lt;/span&gt;

&lt;span class="c"&gt;# Install the plugin&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;install &lt;/span&gt;rexray/ebs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;EBS_ACCESSKEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AKIAIOSFODNN7EXAMPLE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;EBS_SECRETKEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

&lt;span class="c"&gt;# Create an EBS-backed volume&lt;/span&gt;
docker volume create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; rexray/ebs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gp3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nv"&gt;iops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000 &lt;span class="se"&gt;\&lt;/span&gt;
  myapp-data

&lt;span class="c"&gt;# Use it — exactly like a regular volume&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; myapp-data:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
  postgres:16

&lt;span class="c"&gt;# If the host crashes, detach and reattach the EBS volume to a new host&lt;/span&gt;
&lt;span class="c"&gt;# Data is fully safe on AWS!&lt;/span&gt;

&lt;span class="c"&gt;# For Azure: the cloudstor plugin is available&lt;/span&gt;
&lt;span class="c"&gt;# For GCP: use the gcepd driver&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Production Reality: Cloud Volumes = True Persistence.&lt;/strong&gt; Local named volumes are tied to the Docker host — if the host machine is lost, the volumes are gone (EBS is not). In real production, use &lt;strong&gt;AWS EFS/EBS&lt;/strong&gt;, &lt;strong&gt;Azure Managed Disks&lt;/strong&gt;, or &lt;strong&gt;GCP Persistent Disk&lt;/strong&gt; via volume drivers. Or better yet, use managed databases (RDS, Atlas) — that is the preferred approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Volume Best Practices Summary.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Production databases:&lt;/strong&gt; Named volumes or cloud-backed volumes (prefer RDS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Development:&lt;/strong&gt; Bind mounts for code, named volumes for databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets/session data:&lt;/strong&gt; tmpfs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups:&lt;/strong&gt; Automated cron + pg_dump/mongodump stored on separate storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never:&lt;/strong&gt; Store critical data only in the container's writable layer&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also make sure your &lt;code&gt;.env&lt;/code&gt; files are correctly structured — you can validate them with the &lt;a href="https://opscanopy.com/env-example-checker/" rel="noopener noreferrer"&gt;.env Example Checker&lt;/a&gt; before deploying. Once your stack is wired up, put these skills to work with real scenarios in &lt;a href="https://opscanopy.com/learn/guides/devops-projects/" rel="noopener noreferrer"&gt;Hands-on DevOps Projects&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Docker Compose Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
&lt;/p&gt;
One Compose file declares the full application stack; &lt;code&gt;docker compose up&lt;/code&gt; creates all services on a shared network with automatic DNS so containers resolve each other by service name.



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

&lt;p&gt;Real-world applications do not fit in a single container. A MERN stack application requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB&lt;/strong&gt; — database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express/Node&lt;/strong&gt; — backend API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; — frontend (served via Nginx)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; — caching / sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without Compose, everything must be managed manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Without Compose — run all of this by hand!&lt;/span&gt;
docker network create myapp-net

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; mongodb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; myapp-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; mongo_data:/data/db &lt;span class="se"&gt;\&lt;/span&gt;
  mongo:7.0

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; myapp-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGODB_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb://admin:secret@mongodb:27017/myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 &lt;span class="se"&gt;\&lt;/span&gt;
  myapp-api:latest

docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; frontend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; myapp-net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:80 &lt;span class="se"&gt;\&lt;/span&gt;
  myapp-frontend:latest

&lt;span class="c"&gt;# Repeat all of this on every restart...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Just as &lt;code&gt;package.json&lt;/code&gt; lists all npm dependencies and &lt;code&gt;npm install&lt;/code&gt; sets everything up — &lt;code&gt;docker-compose.yml&lt;/code&gt; defines all your containers and &lt;code&gt;docker compose up&lt;/code&gt; starts everything in one command.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With Compose, the same setup becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# With Compose — just this!&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# To shut everything down:&lt;/span&gt;
docker compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Convert an existing command with the &lt;a href="https://opscanopy.com/docker-run-to-compose/" rel="noopener noreferrer"&gt;Docker run → Compose converter&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compose v1 vs Compose v2 — What Is the Difference?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Compose v1 (Old)&lt;/th&gt;
&lt;th&gt;Compose v2 (Current)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Command&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker-compose&lt;/code&gt; (with dash)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker compose&lt;/code&gt; (no dash!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installation&lt;/td&gt;
&lt;td&gt;Separate Python binary&lt;/td&gt;
&lt;td&gt;Built-in Docker plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;Deprecated (2023)&lt;/td&gt;
&lt;td&gt;Active, standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Slower&lt;/td&gt;
&lt;td&gt;Faster (Go-based)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full feature set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026 standard&lt;/td&gt;
&lt;td&gt;Do not use&lt;/td&gt;
&lt;td&gt;Use this!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Use &lt;code&gt;docker compose&lt;/code&gt; (no dash) — this is Compose v2 and the current standard. If an older tutorial uses &lt;code&gt;docker-compose&lt;/code&gt; (with a dash), it is using v1 syntax. V2 is mostly backward compatible, but make it a habit to use v2.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check version — should show v2.x.x&lt;/span&gt;
docker compose version
&lt;span class="c"&gt;# Output: Docker Compose version v2.24.5&lt;/span&gt;

&lt;span class="c"&gt;# Old v1 (deprecated — do not use!)&lt;/span&gt;
docker-compose version

&lt;span class="c"&gt;# If the Compose plugin is missing:&lt;/span&gt;
&lt;span class="c"&gt;# Ubuntu/Debian&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;docker-compose-plugin

&lt;span class="c"&gt;# RHEL/CentOS/Fedora&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;docker-compose-plugin

&lt;span class="c"&gt;# macOS (auto-bundled with Docker Desktop)&lt;/span&gt;
&lt;span class="c"&gt;# Windows (auto-bundled with Docker Desktop)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compose vs Kubernetes — When to Use Which?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Compose&lt;/th&gt;
&lt;th&gt;Kubernetes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Local development&lt;/td&gt;
&lt;td&gt;Perfect choice&lt;/td&gt;
&lt;td&gt;Overkill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small apps (&amp;lt; 5 containers)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Overkill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single server deployment&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-scaling&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-host clusters&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-downtime deploys&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production at scale&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Easy (YAML)&lt;/td&gt;
&lt;td&gt;Steep&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup / small teams&lt;/td&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Overkill initially&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Start with Compose. Run a MERN stack locally using Compose; Compose is sufficient for a single VPS in production too. When traffic grows and auto-scaling becomes necessary, then invest in learning Kubernetes. The progression is: &lt;strong&gt;Docker → Compose → Kubernetes&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Compose File Structure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Annotated docker-compose.yml Structure
&lt;/h3&gt;

&lt;p&gt;Understanding the full structure allows you to read any Compose file with ease:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="c1"&gt;# In Compose v2, the version field is optional/deprecated&lt;/span&gt;
&lt;span class="c1"&gt;# version: '3.8'  ← no longer required!&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# All containers are defined here&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;             &lt;span class="c1"&gt;# Service name (also becomes the DNS hostname!)&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;   &lt;span class="c1"&gt;# Which image to use&lt;/span&gt;
    &lt;span class="c1"&gt;# --- OR ---&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;       &lt;span class="c1"&gt;# Build from a local Dockerfile&lt;/span&gt;

  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;             &lt;span class="c1"&gt;# Another service&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./api&lt;/span&gt;               &lt;span class="c1"&gt;# Build context folder&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.prod&lt;/span&gt;  &lt;span class="c1"&gt;# Specific Dockerfile name&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;              &lt;span class="c1"&gt;# Database service&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# Define custom Docker networks&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="c1"&gt;# Frontend network (public-facing)&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="c1"&gt;# Backend network (private, for DB communication)&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;           &lt;span class="c1"&gt;# Named volumes (data persistence)&lt;/span&gt;
  &lt;span class="na"&gt;db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="c1"&gt;# Database files stored here&lt;/span&gt;
  &lt;span class="na"&gt;redis_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# Redis data persistence&lt;/span&gt;

&lt;span class="na"&gt;configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;           &lt;span class="c1"&gt;# Inject config files&lt;/span&gt;
  &lt;span class="na"&gt;nginx_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf&lt;/span&gt;

&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;           &lt;span class="c1"&gt;# Sensitive data (mainly for Swarm mode)&lt;/span&gt;
  &lt;span class="na"&gt;db_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./secrets/db_password.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Service Name = DNS Hostname!&lt;/strong&gt; In Compose, the service name automatically becomes the DNS hostname. If a service is named &lt;code&gt;db&lt;/code&gt;, other containers can connect to it via &lt;code&gt;db:5432&lt;/code&gt; — not &lt;code&gt;localhost&lt;/code&gt; or an IP. Compose handles this automatically via Docker networking.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  YAML Best Practices in Compose
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Use quotes for strings containing special characters&lt;/span&gt;
&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL="postgresql://user:pass@db:5432/mydb"&lt;/span&gt;

&lt;span class="c1"&gt;# Use literal block (|) for multiline commands&lt;/span&gt;
&lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;sh -c "&lt;/span&gt;
    &lt;span class="s"&gt;until pg_isready; do sleep 1; done&lt;/span&gt;
    &lt;span class="s"&gt;python manage.py migrate&lt;/span&gt;
    &lt;span class="s"&gt;python manage.py runserver 0.0.0.0:8000&lt;/span&gt;
  &lt;span class="s"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# YAML Anchors — avoid repetition (DRY principle)&lt;/span&gt;
&lt;span class="na"&gt;x-common-env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;common-env&lt;/span&gt;      &lt;span class="c1"&gt;# &amp;amp;common-env = define the anchor&lt;/span&gt;
  &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
  &lt;span class="na"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;
  &lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Asia/Kolkata&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*common-env&lt;/span&gt;          &lt;span class="c1"&gt;# *common-env = use the anchor (merge)&lt;/span&gt;
      &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
      &lt;span class="na"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;

  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*common-env&lt;/span&gt;          &lt;span class="c1"&gt;# Reuse the same env vars&lt;/span&gt;
      &lt;span class="na"&gt;WORKER_TYPE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;email&lt;/span&gt;
      &lt;span class="na"&gt;CONCURRENCY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;YAML Anchors — DRY Principle.&lt;/strong&gt; YAML anchors (&lt;code&gt;&amp;amp;name&lt;/code&gt;) and aliases (&lt;code&gt;*name&lt;/code&gt;) let you write common configuration once and reuse it across multiple services. &lt;code&gt;&amp;lt;&amp;lt;: *common-env&lt;/code&gt; means "merge all key-value pairs here." This is the same as the spread operator (&lt;code&gt;...commonProps&lt;/code&gt;) in JavaScript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Override Files Pattern — Multiple Environments
&lt;/h3&gt;

&lt;p&gt;One codebase, different configurations per environment — this pattern is used in production-grade applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File structure:&lt;/span&gt;
docker-compose.yml              &lt;span class="c"&gt;# Base config (common settings)&lt;/span&gt;
docker-compose.override.yml     &lt;span class="c"&gt;# Dev overrides — AUTOMATICALLY loaded!&lt;/span&gt;
docker-compose.dev.yml          &lt;span class="c"&gt;# Development specifics&lt;/span&gt;
docker-compose.prod.yml         &lt;span class="c"&gt;# Production specifics&lt;/span&gt;
docker-compose.test.yml         &lt;span class="c"&gt;# Testing / CI specifics&lt;/span&gt;

&lt;span class="c"&gt;# Development (auto-loads base + override):&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Production (specify files explicitly):&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.prod.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Testing:&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.test.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Validate the final merged config:&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.prod.yml config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (base)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-api:latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# docker-compose.override.yml (dev — auto-loaded!)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;                    &lt;span class="c1"&gt;# Build locally in dev&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./src:/app/src&lt;/span&gt;          &lt;span class="c1"&gt;# Bind mount for hot reload&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
      &lt;span class="na"&gt;DEBUG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:5000"&lt;/span&gt;             &lt;span class="c1"&gt;# Expose port directly in dev&lt;/span&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# docker-compose.prod.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json-file&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Best Practice: Override Files.&lt;/strong&gt; Keep common configuration in the base &lt;code&gt;docker-compose.yml&lt;/code&gt;. Put environment-specific settings in override files. &lt;code&gt;docker-compose.override.yml&lt;/code&gt; loads automatically for development — place bind mounts, debug ports, and dev-only tools there. The production file should contain resource limits, proper restart policies, and logging configuration.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Service Configuration Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complete Service Configuration Reference
&lt;/h3&gt;

&lt;p&gt;All the options available in a service, annotated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;myapp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# IMAGE vs BUILD — choose one&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# Option 1: Use a pre-built image&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.25-alpine&lt;/span&gt;

    &lt;span class="c1"&gt;# Option 2: Build from a local Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;                    &lt;span class="c1"&gt;# Build context (folder with Dockerfile)&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;        &lt;span class="c1"&gt;# Which Dockerfile? (default: Dockerfile)&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                         &lt;span class="c1"&gt;# Build-time arguments (ARG in Dockerfile)&lt;/span&gt;
        &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
        &lt;span class="na"&gt;APP_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0.0"&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;            &lt;span class="c1"&gt;# Specific stage in a multi-stage build&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# CONTAINER NAME&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-nginx&lt;/span&gt;
    &lt;span class="c1"&gt;# Warning: setting container_name prevents scaling!&lt;/span&gt;
    &lt;span class="c1"&gt;# docker compose up --scale myapp=3 will fail if container_name is set&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# PORTS — host:container&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:80"&lt;/span&gt;                   &lt;span class="c1"&gt;# host 8080 → container 80&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;                   &lt;span class="c1"&gt;# HTTPS&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:9090:9090"&lt;/span&gt;       &lt;span class="c1"&gt;# Localhost only (secure!)&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# ENVIRONMENT VARIABLES&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
      &lt;span class="na"&gt;DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;                   &lt;span class="c1"&gt;# Use the service name — not localhost!&lt;/span&gt;
      &lt;span class="na"&gt;DB_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432"&lt;/span&gt;               &lt;span class="c1"&gt;# Use quotes for numeric values&lt;/span&gt;
      &lt;span class="na"&gt;DB_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mydb&lt;/span&gt;

    &lt;span class="c1"&gt;# Load from env_file&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;                        &lt;span class="c1"&gt;# Main .env file&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env.production&lt;/span&gt;             &lt;span class="c1"&gt;# Production-specific variables&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# VOLUMES — data persistence and bind mounts&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./src:/app/src:ro&lt;/span&gt;           &lt;span class="c1"&gt;# Bind mount, read-only (dev hot reload)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_data:/var/lib/postgresql/data&lt;/span&gt;  &lt;span class="c1"&gt;# Named volume (production data)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;  &lt;span class="c1"&gt;# Host timezone inside container&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# NETWORKS&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;                    &lt;span class="c1"&gt;# Public-facing network&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;                     &lt;span class="c1"&gt;# Private network (for DB communication)&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# DEPENDS_ON — startup order&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;  &lt;span class="c1"&gt;# Wait until db is healthy&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_started&lt;/span&gt;  &lt;span class="c1"&gt;# Just started, not necessarily healthy&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# RESTART POLICY&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="c1"&gt;# Options:&lt;/span&gt;
    &lt;span class="c1"&gt;# no           — do not restart (default)&lt;/span&gt;
    &lt;span class="c1"&gt;# always       — always restart&lt;/span&gt;
    &lt;span class="c1"&gt;# on-failure   — restart only on failure&lt;/span&gt;
    &lt;span class="c1"&gt;# unless-stopped — always restart unless manually stopped&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# COMMAND and ENTRYPOINT&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm start&lt;/span&gt;              &lt;span class="c1"&gt;# String form&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server.js"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Array form (preferred — shell injection safe)&lt;/span&gt;

    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./docker-entrypoint.sh"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# HEALTHCHECK&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;       &lt;span class="c1"&gt;# Check every 30 seconds&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;        &lt;span class="c1"&gt;# Response must arrive within 10 seconds&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;          &lt;span class="c1"&gt;# Mark unhealthy after 3 failures&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;40s&lt;/span&gt;   &lt;span class="c1"&gt;# Wait this long after startup before checking&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# DEPLOY — resource limits&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.50"&lt;/span&gt;            &lt;span class="c1"&gt;# Max 50% of one CPU core&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;            &lt;span class="c1"&gt;# Max 512 MB RAM&lt;/span&gt;
        &lt;span class="na"&gt;reservations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.25"&lt;/span&gt;            &lt;span class="c1"&gt;# Minimum guaranteed CPU&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256M&lt;/span&gt;            &lt;span class="c1"&gt;# Minimum guaranteed RAM&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# PROFILES — optional services&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;debug&lt;/span&gt;   &lt;span class="c1"&gt;# Only starts with: docker compose --profile debug up&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# LOGGING&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json-file&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10m"&lt;/span&gt;     &lt;span class="c1"&gt;# Log file max 10 MB&lt;/span&gt;
        &lt;span class="na"&gt;max-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;       &lt;span class="c1"&gt;# Max 3 log files (then rotate)&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# EXTRA HOSTS — /etc/hosts entries&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;extra_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host.docker.internal:host-gateway"&lt;/span&gt;  &lt;span class="c1"&gt;# Connect to host machine&lt;/span&gt;

    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="c1"&gt;# DNS — custom DNS servers&lt;/span&gt;
    &lt;span class="c1"&gt;# ──────────────────────────────────────────────&lt;/span&gt;
    &lt;span class="na"&gt;dns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8.8.8.8&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8.8.4.4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  depends_on and Healthchecks — A Common Gotcha
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;The Default depends_on Behavior Is Misleading!&lt;/strong&gt; A simple &lt;code&gt;depends_on: [db]&lt;/code&gt; only guarantees that the &lt;code&gt;db&lt;/code&gt; container has &lt;em&gt;started&lt;/em&gt; — not that it is &lt;em&gt;ready&lt;/em&gt;. PostgreSQL takes 5–10 seconds to initialize. If the &lt;code&gt;api&lt;/code&gt; service tries to connect immediately, the connection will fail. Solution: use &lt;code&gt;condition: service_healthy&lt;/code&gt; and define a &lt;code&gt;healthcheck&lt;/code&gt; on the db service.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;myuser&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mydb"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;    &lt;span class="c1"&gt;# Give PostgreSQL time to initialize&lt;/span&gt;

  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;   &lt;span class="c1"&gt;# Wait until HEALTHY, not just STARTED!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Profiles — Make Dev Tools Optional.&lt;/strong&gt; Using &lt;code&gt;profiles&lt;/code&gt;, you can make certain services optional. For example, &lt;code&gt;pgadmin&lt;/code&gt; (a DB GUI) is only needed in development — give it &lt;code&gt;profiles: [debug]&lt;/code&gt;. It will not start during a normal &lt;code&gt;docker compose up&lt;/code&gt;. It will only start with &lt;code&gt;docker compose --profile debug up&lt;/code&gt;. This prevents debug tools from accidentally running in production.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Compose Commands — Complete Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  docker compose up — Start Everything
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic — start in foreground (Ctrl+C to stop)&lt;/span&gt;
docker compose up

&lt;span class="c"&gt;# Detached — start in background (for production)&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Rebuild images and start (after code changes)&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;

&lt;span class="c"&gt;# Force recreate containers (even if nothing changed)&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;--force-recreate&lt;/span&gt;

&lt;span class="c"&gt;# Start only specific services&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; api db

&lt;span class="c"&gt;# Run multiple instances (for load balancing)&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--scale&lt;/span&gt; &lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
&lt;span class="c"&gt;# Note: container_name must not be set when scaling!&lt;/span&gt;

&lt;span class="c"&gt;# Start with a specific profile&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; debug up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Use a custom .env file&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env.prod up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Remove orphan containers (not defined in the compose file)&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--remove-orphans&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker compose down — Stop and Clean Up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Standard — remove containers and networks&lt;/span&gt;
docker compose down

&lt;span class="c"&gt;# Also remove volumes (DATA LOSS — be careful!)&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt;

&lt;span class="c"&gt;# Also remove images&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;--rmi&lt;/span&gt; all     &lt;span class="c"&gt;# All images&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;--rmi&lt;/span&gt; &lt;span class="nb"&gt;local&lt;/span&gt;   &lt;span class="c"&gt;# Only locally built images&lt;/span&gt;

&lt;span class="c"&gt;# Stop but keep containers (do not remove)&lt;/span&gt;
docker compose stop

&lt;span class="c"&gt;# Complete teardown — clean up everything&lt;/span&gt;
docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;--rmi&lt;/span&gt; all &lt;span class="nt"&gt;--remove-orphans&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;docker compose down -v — Data Will Be Deleted!&lt;/strong&gt; The &lt;code&gt;-v&lt;/code&gt; flag removes all named volumes. This means all database data is gone. This is useful for a clean restart in development, but never use it carelessly in production. Take a backup first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  docker compose logs — Debugging
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Logs from all services&lt;/span&gt;
docker compose logs

&lt;span class="c"&gt;# Follow (live stream)&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Logs from a specific service&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; api

&lt;span class="c"&gt;# Last 100 lines&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 100

&lt;span class="c"&gt;# With timestamps&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-t&lt;/span&gt;

&lt;span class="c"&gt;# Multiple services at once&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; api db

&lt;span class="c"&gt;# Last 1 hour&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;--since&lt;/span&gt; 1h

&lt;span class="c"&gt;# From a specific time&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"2024-01-15T10:00:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker compose exec vs run — What Is the Difference?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;exec&lt;/th&gt;
&lt;th&gt;run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Container&lt;/td&gt;
&lt;td&gt;Runs inside an existing running container&lt;/td&gt;
&lt;td&gt;Starts a new container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use case&lt;/td&gt;
&lt;td&gt;Debug a running service&lt;/td&gt;
&lt;td&gt;One-off commands (migrations, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service running?&lt;/td&gt;
&lt;td&gt;Must be running&lt;/td&gt;
&lt;td&gt;Service does not need to be running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cleanup&lt;/td&gt;
&lt;td&gt;Container remains as-is&lt;/td&gt;
&lt;td&gt;Auto-cleanup with &lt;code&gt;--rm&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ── docker compose exec ──────────────────────────────&lt;/span&gt;
&lt;span class="c"&gt;# Run a command in a running container&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;api bash
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;db psql &lt;span class="nt"&gt;-U&lt;/span&gt; admin &lt;span class="nt"&gt;-d&lt;/span&gt; mydb
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;redis redis-cli

&lt;span class="c"&gt;# Without TTY (for automation/scripts)&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; api npm &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# As a specific user&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--user&lt;/span&gt; root api bash

&lt;span class="c"&gt;# ── docker compose run ──────────────────────────────&lt;/span&gt;
&lt;span class="c"&gt;# One-off command in a new container&lt;/span&gt;
docker compose run api npm run migrate
docker compose run api python manage.py createsuperuser

&lt;span class="c"&gt;# Auto-cleanup after run&lt;/span&gt;
docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; api npm &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# Override the entrypoint&lt;/span&gt;
docker compose run &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; bash api

&lt;span class="c"&gt;# With custom env vars&lt;/span&gt;
docker compose run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;api npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;exec vs run — Simple Rule.&lt;/strong&gt; Need to do something in a running service (debug, inspect)? → &lt;code&gt;exec&lt;/code&gt;. Need to run a one-off task alongside the service (DB migration, seed data, test run)? → &lt;code&gt;run --rm&lt;/code&gt;. &lt;code&gt;docker compose run --rm web python manage.py migrate&lt;/code&gt; is the standard pattern for Django/Rails migrations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Other Useful Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List services&lt;/span&gt;
docker compose ps
docker compose ps &lt;span class="nt"&gt;-a&lt;/span&gt;             &lt;span class="c"&gt;# Include stopped services&lt;/span&gt;
docker compose ps &lt;span class="nt"&gt;--format&lt;/span&gt; json  &lt;span class="c"&gt;# JSON output&lt;/span&gt;

&lt;span class="c"&gt;# Build only (do not start)&lt;/span&gt;
docker compose build
docker compose build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; api      &lt;span class="c"&gt;# Ignore cache&lt;/span&gt;
docker compose build &lt;span class="nt"&gt;--parallel&lt;/span&gt;          &lt;span class="c"&gt;# Build all in parallel&lt;/span&gt;

&lt;span class="c"&gt;# Pull latest images&lt;/span&gt;
docker compose pull

&lt;span class="c"&gt;# Push images to registry&lt;/span&gt;
docker compose push

&lt;span class="c"&gt;# Validate the compose file (shows final merged config)&lt;/span&gt;
docker compose config

&lt;span class="c"&gt;# Show running processes&lt;/span&gt;
docker compose top

&lt;span class="c"&gt;# Restart services&lt;/span&gt;
docker compose restart
docker compose restart api

&lt;span class="c"&gt;# Pause/Unpause (stop CPU usage, keep memory)&lt;/span&gt;
docker compose pause
docker compose unpause

&lt;span class="c"&gt;# Create without starting&lt;/span&gt;
docker compose create

&lt;span class="c"&gt;# Stream events&lt;/span&gt;
docker compose events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Validate Your Compose File.&lt;/strong&gt; &lt;code&gt;docker compose config&lt;/code&gt; is a very useful command that merges all override files and shows the final YAML. Catch typos, missing variables, and syntax errors before deploying. Use it in CI/CD too: &lt;code&gt;docker compose -f docker-compose.yml -f docker-compose.prod.yml config --quiet&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Production Compose Examples — Real-World Stacks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example 1: MERN Stack (React + Node + MongoDB)
&lt;/h3&gt;

&lt;p&gt;Most relevant for React developers — a complete MERN stack with Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml — MERN Stack&lt;/span&gt;
&lt;span class="c1"&gt;# Usage: docker compose up -d&lt;/span&gt;
&lt;span class="c1"&gt;# Variables are loaded from a .env file&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# MongoDB Database&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;mongodb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo:7.0-jammy&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mern-mongodb&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_USER:-admin}&lt;/span&gt;
      &lt;span class="na"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_PASSWORD:-password}&lt;/span&gt;
      &lt;span class="na"&gt;MONGO_INITDB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MONGO_DB:-myapp}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo_data:/data/db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo_config:/data/configdb&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./mongo-init.js:/docker-entrypoint-initdb.d/init.js:ro&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;          &lt;span class="c1"&gt;# Backend network only — no public access!&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mongosh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--eval"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db.adminCommand('ping')"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json-file&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10m"&lt;/span&gt;
        &lt;span class="na"&gt;max-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Node.js API Server&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./api&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;    &lt;span class="c1"&gt;# Use the production stage of a multi-stage build&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mern-api&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
      &lt;span class="na"&gt;MONGODB_URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb://${MONGO_USER:-admin}:${MONGO_PASSWORD:-password}@mongodb:27017/${MONGO_DB:-myapp}?authSource=admin&lt;/span&gt;
      &lt;span class="na"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${JWT_SECRET:-changeme-in-production}&lt;/span&gt;
      &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api_logs:/app/logs&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;              &lt;span class="c1"&gt;# For MongoDB communication&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;             &lt;span class="c1"&gt;# For receiving requests from React&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mongodb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-e"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;require('http').get('http://localhost:5000/health',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;process.exit(r.statusCode&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;===&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;200&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1))"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;20s&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.50"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json-file&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10m"&lt;/span&gt;
        &lt;span class="na"&gt;max-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# React Frontend (served via Nginx)&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;REACT_APP_API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${API_URL:-http://localhost:5000}&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mern-frontend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${FRONTEND_PORT:-3000}:80"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wget"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-qO-"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

&lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
&lt;span class="c1"&gt;# Volumes&lt;/span&gt;
&lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mongo_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com.myapp.description=MongoDB&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;volume"&lt;/span&gt;
  &lt;span class="na"&gt;mongo_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api_logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
&lt;span class="c1"&gt;# Networks&lt;/span&gt;
&lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;             &lt;span class="c1"&gt;# Public traffic comes here&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;             &lt;span class="c1"&gt;# No external access! Container-to-container only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Production Pattern: Internal Networks.&lt;/strong&gt; Apply &lt;code&gt;internal: true&lt;/code&gt; to the backend network. This means MongoDB is never directly accessible from the internet — only the &lt;code&gt;api&lt;/code&gt; service can reach it. This is a security best practice. The flow is: Frontend → API (via frontend network), API → MongoDB (via backend network). MongoDB has no public port exposed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Example 2: Python Flask + PostgreSQL + Redis + Celery
&lt;/h3&gt;

&lt;p&gt;Full-stack Python application with background task processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml — Flask + Postgres + Redis + Celery&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# PostgreSQL Database&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_USER:-myuser}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_PASSWORD:-mypassword}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_NAME:-mydb}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${DB_USER:-myuser}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${DB_NAME:-mydb}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Redis — Cache + Message Broker&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-redis_secret}&lt;/span&gt;
    &lt;span class="c1"&gt;# appendonly yes = persist data even after restart&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_data:/data&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--pass"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${REDIS_PASSWORD:-redis_secret}"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Flask Web Application&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://${DB_USER:-myuser}:${DB_PASSWORD:-mypassword}@postgres:5432/${DB_NAME:-mydb}&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379/0&lt;/span&gt;
      &lt;span class="na"&gt;FLASK_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${FLASK_ENV:-production}&lt;/span&gt;
      &lt;span class="na"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${SECRET_KEY:-change-in-production}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${APP_PORT:-5000}:5000"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app_logs:/app/logs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./uploads:/app/uploads&lt;/span&gt;   &lt;span class="c1"&gt;# Persist user uploads&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:5000/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;20s&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Celery Worker — Background Tasks&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;celery_worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;    &lt;span class="c1"&gt;# Same image as web!&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;celery -A app.celery worker --loglevel=info --concurrency=4&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://${DB_USER:-myuser}:${DB_PASSWORD:-mypassword}@postgres:5432/${DB_NAME:-mydb}&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379/0&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Celery Beat — Scheduled Tasks (Cron-like)&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;celery_beat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;celery -A app.celery beat --loglevel=info&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://${DB_USER:-myuser}:${DB_PASSWORD:-mypassword}@postgres:5432/${DB_NAME:-mydb}&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://:${REDIS_PASSWORD:-redis_secret}@redis:6379/0&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="c1"&gt;# Beat should have only ONE instance — do not scale it!&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Nginx Reverse Proxy&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.25-alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/ssl:/etc/nginx/ssl:ro&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app_logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;    &lt;span class="c1"&gt;# DB and Redis must not be directly accessible!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Same Image, Different Command!&lt;/strong&gt; Flask web, Celery worker, and Celery beat all use the same &lt;code&gt;build: .&lt;/code&gt; (same Docker image) but with different &lt;code&gt;command&lt;/code&gt; values. This is efficient: build one image, run different processes. When code changes, only one image needs to be rebuilt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Example 3: WordPress + MySQL
&lt;/h3&gt;

&lt;p&gt;Classic CMS setup — useful for client projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml — WordPress + MySQL + Nginx&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# MySQL Database&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;mysql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:8.0&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MYSQL_ROOT_PASSWORD:-rootpassword}&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MYSQL_PASSWORD:-wppassword}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mysql_data:/var/lib/mysql&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wp-network&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mysqladmin"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-h"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-u"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wordpress"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-pwppassword"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# WordPress (PHP-FPM)&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;wordpress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress:6.4-php8.2-fpm-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;WORDPRESS_DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:3306&lt;/span&gt;      &lt;span class="c1"&gt;# Use the service name!&lt;/span&gt;
      &lt;span class="na"&gt;WORDPRESS_DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
      &lt;span class="na"&gt;WORDPRESS_DB_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${MYSQL_PASSWORD:-wppassword}&lt;/span&gt;
      &lt;span class="na"&gt;WORDPRESS_DB_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wp_content:/var/www/html/wp-content&lt;/span&gt;  &lt;span class="c1"&gt;# Themes, plugins, uploads&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mysql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wp-network&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Nginx — Serve static files + PHP-FPM proxy&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.25-alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/wordpress.conf:/etc/nginx/conf.d/default.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wp_content:/var/www/html/wp-content:ro&lt;/span&gt;   &lt;span class="c1"&gt;# Serve static files directly&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wp-network&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mysql_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;# Database files&lt;/span&gt;
  &lt;span class="na"&gt;wp_content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="c1"&gt;# WordPress uploads, themes, plugins&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;wp-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 4: Monitoring Stack (Prometheus + Grafana + cAdvisor)
&lt;/h3&gt;

&lt;p&gt;Monitoring production applications is essential. This stack tracks CPU, memory, and network usage of Docker containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml — Monitoring Stack&lt;/span&gt;
&lt;span class="c1"&gt;# Usage: docker compose up -d&lt;/span&gt;
&lt;span class="c1"&gt;# Access: Grafana → http://localhost:3000 (admin/admin)&lt;/span&gt;
&lt;span class="c1"&gt;#         Prometheus → http://localhost:9090&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Prometheus — Metrics Collector&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus_data:/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--config.file=/etc/prometheus/prometheus.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--storage.tsdb.path=/prometheus&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--web.enable-lifecycle&lt;/span&gt;         &lt;span class="c1"&gt;# Enable config hot-reload&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Grafana — Visualization Dashboard&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GF_SECURITY_ADMIN_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GRAFANA_USER:-admin}&lt;/span&gt;
      &lt;span class="na"&gt;GF_SECURITY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GRAFANA_PASSWORD:-admin}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;grafana_data:/var/lib/grafana&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./grafana/dashboards:/etc/grafana/provisioning/dashboards:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./grafana/datasources:/etc/grafana/provisioning/datasources:ro&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# cAdvisor — Container Metrics&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;cadvisor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/cadvisor/cadvisor:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/:/rootfs:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run:/var/run:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/sys:/sys:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker/:/var/lib/docker:ro&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="c1"&gt;# cAdvisor communicates directly with the Docker daemon&lt;/span&gt;
    &lt;span class="c1"&gt;# which is why host paths need to be mounted&lt;/span&gt;

  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="c1"&gt;# Node Exporter — Host Machine Metrics&lt;/span&gt;
  &lt;span class="c1"&gt;# ────────────────────────────────&lt;/span&gt;
  &lt;span class="na"&gt;node_exporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/node-exporter:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/proc:/host/proc:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/sys:/host/sys:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/:/rootfs:ro&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--path.procfs=/host/proc&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--path.sysfs=/host/sys&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9100:9100"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="c1"&gt;# Node Exporter exposes host CPU, RAM, disk, and network metrics&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# Time-series metrics data&lt;/span&gt;
  &lt;span class="na"&gt;grafana_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# Dashboards and settings&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;What the Monitoring Stack Tracks.&lt;/strong&gt; &lt;strong&gt;cAdvisor&lt;/strong&gt; — CPU/RAM/network/disk usage for every Docker container. &lt;strong&gt;Node Exporter&lt;/strong&gt; — hardware metrics for the host machine. &lt;strong&gt;Prometheus&lt;/strong&gt; — collects and stores all data. &lt;strong&gt;Grafana&lt;/strong&gt; — visualizes everything in dashboards. This monitoring setup is essential in production: you cannot improve what you cannot measure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Production Checklist for Compose Apps.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;restart: unless-stopped&lt;/code&gt; to all services&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;healthcheck&lt;/code&gt; on database services&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;internal: true&lt;/code&gt; on networks for sensitive services&lt;/li&gt;
&lt;li&gt;Store passwords in a &lt;code&gt;.env&lt;/code&gt; file — never hardcode them in the compose file&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;.env&lt;/code&gt; file to &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set resource limits (&lt;code&gt;deploy.resources.limits&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Configure log rotation (&lt;code&gt;max-size&lt;/code&gt;, &lt;code&gt;max-file&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Keep images updated with regular &lt;code&gt;docker compose pull&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Container Registries
&lt;/h2&gt;

&lt;p&gt;Just as &lt;strong&gt;npm registry&lt;/strong&gt; stores packages, a Docker registry stores images. You build an image, push it to a registry, and anyone in the world can pull it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A Docker registry is a storage and distribution system for Docker images. Docker Hub is the default public registry, but you can use private registries for proprietary images. Think of it as GitHub for code — but for images.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Image Naming Convention
&lt;/h3&gt;

&lt;p&gt;Every Docker image has a fully qualified name. The format is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;registry/][username/]image[:tag][@digest]

&lt;span class="c"&gt;# Examples — from simple to fully qualified:&lt;/span&gt;
ubuntu                                                        &lt;span class="c"&gt;# Docker Hub official (implicit library/)&lt;/span&gt;
nginx:1.25-alpine                                             &lt;span class="c"&gt;# Docker Hub official with tag&lt;/span&gt;
library/ubuntu:22.04                                          &lt;span class="c"&gt;# Explicit official image path&lt;/span&gt;
username/myapp:v1.0                                           &lt;span class="c"&gt;# Docker Hub user image&lt;/span&gt;

&lt;span class="c"&gt;# Cloud registries&lt;/span&gt;
ghcr.io/username/myapp:latest                                 &lt;span class="c"&gt;# GitHub Container Registry&lt;/span&gt;
123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.0         &lt;span class="c"&gt;# AWS ECR (region-scoped)&lt;/span&gt;
asia.gcr.io/myproject/myapp:latest                            &lt;span class="c"&gt;# Google Container Registry&lt;/span&gt;
registry.gitlab.com/group/project/myapp:1.0                   &lt;span class="c"&gt;# GitLab Registry&lt;/span&gt;

&lt;span class="c"&gt;# Digest (immutable — safer than a tag!)&lt;/span&gt;
ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Image Tagging Strategy
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag Type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Stable?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myapp:latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Development&lt;/td&gt;
&lt;td&gt;No — changes!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic Version&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myapp:v1.2.3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Production releases&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git SHA&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myapp:a3f1b2c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CI/CD traceability&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branch name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myapp:main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Branch-based testing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Date-based&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myapp:20240115&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nightly builds&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;myapp@sha256:abc...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pinned dependencies&lt;/td&gt;
&lt;td&gt;Immutable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;Avoid the latest Tag!&lt;/strong&gt; Using the &lt;code&gt;latest&lt;/code&gt; tag in production is dangerous. If a new image is pushed, your deployment may update unexpectedly. Always use specific version tags in production — &lt;code&gt;myapp:v1.2.3&lt;/code&gt; or a Git SHA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Best Practice: Multiple Tags.&lt;/strong&gt; Tag a single image with multiple tags: &lt;code&gt;myapp:v1.2.3&lt;/code&gt;, &lt;code&gt;myapp:v1.2&lt;/code&gt;, &lt;code&gt;myapp:v1&lt;/code&gt;, and &lt;code&gt;myapp:latest&lt;/code&gt;. This gives users flexibility while maintaining immutable specific versions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Hub — The Default Registry
&lt;/h2&gt;

&lt;p&gt;Docker Hub is the default public registry — like npm for JavaScript packages. Start with a free account and explore private registries once you hit the limits.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Create an account at &lt;a href="https://hub.docker.com" rel="noopener noreferrer"&gt;hub.docker.com&lt;/a&gt;. Free accounts include 1 private repository and unlimited public repositories. Use access tokens rather than passwords — they are safer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Complete Docker Hub Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Login to Docker Hub&lt;/span&gt;
docker login
&lt;span class="c"&gt;# Username: yourname&lt;/span&gt;
&lt;span class="c"&gt;# Password: (enter password or access token)&lt;/span&gt;

&lt;span class="c"&gt;# Better: login with an access token (Settings → Security → New Access Token)&lt;/span&gt;
docker login &lt;span class="nt"&gt;-u&lt;/span&gt; username &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"your_access_token_here"&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Tag your local image for Docker Hub&lt;/span&gt;
docker tag myapp username/myapp:v1.0.0
docker tag myapp username/myapp:latest

&lt;span class="c"&gt;# Step 3: Push to Docker Hub&lt;/span&gt;
docker push username/myapp:v1.0.0
docker push username/myapp:latest

&lt;span class="c"&gt;# Step 4: Pull from anywhere in the world&lt;/span&gt;
docker pull username/myapp:v1.0.0

&lt;span class="c"&gt;# Step 5: Logout (important on shared machines!)&lt;/span&gt;
docker &lt;span class="nb"&gt;logout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rate Limits — Important for CI/CD
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Account Type&lt;/th&gt;
&lt;th&gt;Pull Limit&lt;/th&gt;
&lt;th&gt;Time Window&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Anonymous (no login)&lt;/td&gt;
&lt;td&gt;100 pulls&lt;/td&gt;
&lt;td&gt;Per 6 hours / IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free account (authenticated)&lt;/td&gt;
&lt;td&gt;200 pulls&lt;/td&gt;
&lt;td&gt;Per 6 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro / Team / Business&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Authenticate pulls in CI/CD to avoid rate limits&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | docker login &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DOCKER_USERNAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;

&lt;span class="c"&gt;# Check your current rate limit status&lt;/span&gt;
&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://auth.docker.io/token?service=registry.docker.io&amp;amp;scope=repository:ratelimitpreview/test:pull"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .token&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--head&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; ratelimit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rate Limit Workaround — Mirror Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/docker/daemon.json — configure a Docker Hub mirror&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/docker/daemon.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registry-mirrors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://mirror.gcr.io"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://registry.docker-cn.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Restart Docker daemon after the config change&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart docker

&lt;span class="c"&gt;# Verify the mirror is active&lt;/span&gt;
docker info | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A2&lt;/span&gt; &lt;span class="s2"&gt;"Registry Mirrors"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;CI/CD Rate Limit Problem.&lt;/strong&gt; If your CI/CD pipeline pulls images anonymously (no &lt;code&gt;docker login&lt;/code&gt;), rate limits are hit quickly — especially in large teams. Solution: always authenticate in CI, or mirror images to your own private registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Use Access Tokens, Not Passwords.&lt;/strong&gt; Docker Hub Settings → Security → New Access Token. Tokens can be given specific permissions (read-only, read-write). If a token is compromised, only that token needs to be revoked — not the full account. Always use access tokens in CI/CD.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Other Registries — AWS ECR, GitHub, GCR
&lt;/h2&gt;

&lt;p&gt;Docker Hub is not the only option. If you deploy to AWS, ECR is the best choice. If you use GitHub Actions, &lt;code&gt;ghcr.io&lt;/code&gt; is a perfect fit. Here is how to use each.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Elastic Container Registry (ECR)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;When to use ECR:&lt;/strong&gt; When your application is deployed to AWS (ECS, EKS, Lambda). ECR images integrate automatically with AWS services — no extra authentication is needed when running on AWS with proper IAM roles.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Prerequisites: AWS CLI installed and configured (aws configure)&lt;/span&gt;

&lt;span class="c"&gt;# Step 1: Authenticate with ECR (generates a 12-hour token)&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1 | &lt;span class="se"&gt;\&lt;/span&gt;
  docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  123456789.dkr.ecr.ap-south-1.amazonaws.com

&lt;span class="c"&gt;# Step 2: Create a repository in ECR&lt;/span&gt;
aws ecr create-repository &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-scanning-configuration&lt;/span&gt; &lt;span class="nv"&gt;scanOnPush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--encryption-configuration&lt;/span&gt; &lt;span class="nv"&gt;encryptionType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AES256

&lt;span class="c"&gt;# Step 3: Tag the image for ECR&lt;/span&gt;
&lt;span class="nv"&gt;ECR_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"123456789.dkr.ecr.ap-south-1.amazonaws.com"&lt;/span&gt;
docker tag myapp:latest &lt;span class="nv"&gt;$ECR_URI&lt;/span&gt;/myapp:latest
docker tag myapp:latest &lt;span class="nv"&gt;$ECR_URI&lt;/span&gt;/myapp:v1.0.0

&lt;span class="c"&gt;# Step 4: Push&lt;/span&gt;
docker push &lt;span class="nv"&gt;$ECR_URI&lt;/span&gt;/myapp:latest
docker push &lt;span class="nv"&gt;$ECR_URI&lt;/span&gt;/myapp:v1.0.0

&lt;span class="c"&gt;# Step 5: Pull (from any authenticated source)&lt;/span&gt;
docker pull &lt;span class="nv"&gt;$ECR_URI&lt;/span&gt;/myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ECR Lifecycle Policy — auto-delete old images (save storage costs!)&lt;/span&gt;
aws ecr put-lifecycle-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--lifecycle-policy&lt;/span&gt; &lt;span class="s1"&gt;'{
    "rules": [{
      "rulePriority": 1,
      "description": "Keep only last 10 images",
      "selection": {
        "tagStatus": "any",
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": {"type": "expire"}
    }]
  }'&lt;/span&gt;

&lt;span class="c"&gt;# List images in ECR repository&lt;/span&gt;
aws ecr describe-images &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'sort_by(imageDetails, &amp;amp;imagePushedAt)[-5:]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Container Registry (ghcr.io)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GITHUB_TOKEN&lt;/code&gt; is automatically available in GitHub Actions — perfect integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Create a Personal Access Token (PAT)&lt;/span&gt;
&lt;span class="c"&gt;# GitHub → Settings → Developer Settings → Personal Access Tokens&lt;/span&gt;
&lt;span class="c"&gt;# Required scopes: read:packages, write:packages, delete:packages&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Login to ghcr.io&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt; | docker login ghcr.io &lt;span class="nt"&gt;-u&lt;/span&gt; USERNAME &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: Tag and push&lt;/span&gt;
docker tag myapp ghcr.io/username/myapp:latest
docker push ghcr.io/username/myapp:latest

&lt;span class="c"&gt;# Make the image public (default is private)&lt;/span&gt;
&lt;span class="c"&gt;# GitHub → Package → Package Settings → Change Visibility&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions — push to ghcr.io (automatic authentication!)&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Push&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;        &lt;span class="c1"&gt;# Required for ghcr.io!&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to GitHub Container Registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;  &lt;span class="c1"&gt;# Auto-available!&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Push&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/${{ github.repository }}/myapp:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Google Container Registry / Artifact Registry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Authenticate gcloud with Docker&lt;/span&gt;
gcloud auth configure-docker asia.gcr.io

&lt;span class="c"&gt;# New Artifact Registry (recommended over GCR)&lt;/span&gt;
gcloud auth configure-docker asia-south1-docker.pkg.dev

&lt;span class="c"&gt;# Tag and push&lt;/span&gt;
docker tag myapp asia.gcr.io/my-project/myapp:v1.0
docker push asia.gcr.io/my-project/myapp:v1.0

&lt;span class="c"&gt;# Artifact Registry (newer, more features)&lt;/span&gt;
docker tag myapp asia-south1-docker.pkg.dev/my-project/my-repo/myapp:v1.0
docker push asia-south1-docker.pkg.dev/my-project/my-repo/myapp:v1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Registry Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Registry&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docker Hub&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Free (limited) / Paid&lt;/td&gt;
&lt;td&gt;Open source, small teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS ECR&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Per storage + transfer&lt;/td&gt;
&lt;td&gt;AWS deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google GCR/AR&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Per storage + transfer&lt;/td&gt;
&lt;td&gt;GCP deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Packages&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Free for public&lt;/td&gt;
&lt;td&gt;Open source + CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harbor&lt;/td&gt;
&lt;td&gt;Self-hosted&lt;/td&gt;
&lt;td&gt;Free (infra cost)&lt;/td&gt;
&lt;td&gt;Enterprise, full control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nexus Repository&lt;/td&gt;
&lt;td&gt;Self-hosted&lt;/td&gt;
&lt;td&gt;Free/Commercial&lt;/td&gt;
&lt;td&gt;Enterprise artifact management&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Deploying to AWS → use ECR (pick the Region closest to your users). Using GitHub Actions → use ghcr.io (free for public repos). Startup or open source → start with the Docker Hub free tier.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Pushing and Pulling Workflows — CI/CD Integration
&lt;/h2&gt;

&lt;p&gt;Integrating the registry workflow with CI/CD is the core of production-ready DevOps. Here is a complete, real-world workflow for a React application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;Workflow Overview:&lt;/strong&gt; Build → Tag (with version + Git SHA) → Authenticate → Push to Registry → Verify. Every step matters. Tagging with the Git SHA lets you know exactly which commit is running in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Complete Registry Push Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Full CI/CD workflow for a React app&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;  &lt;span class="c"&gt;# Exit on any error&lt;/span&gt;

&lt;span class="nv"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"myreactapp"&lt;/span&gt;
&lt;span class="nv"&gt;REGISTRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"123456789.dkr.ecr.ap-south-1.amazonaws.com"&lt;/span&gt;

&lt;span class="c"&gt;# Generate meaningful version tags&lt;/span&gt;
&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git describe &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nt"&gt;--always&lt;/span&gt; &lt;span class="nt"&gt;--dirty&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_SHA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;BRANCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD | &lt;span class="nb"&gt;tr&lt;/span&gt; / -&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building version: &lt;/span&gt;&lt;span class="nv"&gt;$VERSION&lt;/span&gt;&lt;span class="s2"&gt; (SHA: &lt;/span&gt;&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;

&lt;span class="c"&gt;# Step 1: Build with build args for traceability&lt;/span&gt;
docker build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;BUILD_DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;GIT_COMMIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$VERSION&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$VERSION&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Build complete"&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Authenticate with ECR&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1 | &lt;span class="se"&gt;\&lt;/span&gt;
  docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Authenticated with ECR"&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: Tag all versions for registry&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$VERSION&lt;/span&gt; &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$VERSION&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt; &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:latest &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:latest

&lt;span class="c"&gt;# Step 4: Push all tags&lt;/span&gt;
docker push &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$VERSION&lt;/span&gt;
docker push &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt;
docker push &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt;:latest
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"All tags pushed"&lt;/span&gt;

&lt;span class="c"&gt;# Step 5: Verify push was successful&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Recent images in ECR:"&lt;/span&gt;
aws ecr describe-images &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; &lt;span class="nv"&gt;$IMAGE_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'sort_by(imageDetails, &amp;amp;imagePushedAt)[-5:].{Tags:imageTags,Size:imageSizeInBytes,Pushed:imagePushedAt}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Actions — Full CI/CD Pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build, Scan, and Push&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;123456789.dkr.ecr.ap-south-1.amazonaws.com&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myreactapp&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Full history for git describe&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-south-1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Amazon ECR&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecr-login@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate image metadata&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/metadata-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;type=sha,prefix=sha-&lt;/span&gt;
            &lt;span class="s"&gt;type=ref,event=branch&lt;/span&gt;
            &lt;span class="s"&gt;type=semver,pattern={{version}}&lt;/span&gt;
            &lt;span class="s"&gt;type=raw,value=latest,enable={{is_default_branch}}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event_name == 'push' }}&lt;/span&gt;  &lt;span class="c1"&gt;# Only push on merge to main&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.tags }}&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.labels }}&lt;/span&gt;
          &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;          &lt;span class="c1"&gt;# GitHub Actions cache&lt;/span&gt;
          &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;strong&gt;Do Not Push on Pull Requests!&lt;/strong&gt; Pull request builds should only build and test — do not push images. Push only when merging to main/master. That is why &lt;code&gt;push: ${{ github.event_name == 'push' }}&lt;/code&gt; is used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;strong&gt;Speed Up Builds with Cache.&lt;/strong&gt; &lt;code&gt;cache-from: type=gha&lt;/code&gt; and &lt;code&gt;cache-to: type=gha,mode=max&lt;/code&gt; use GitHub Actions cache. The first build will be slow; subsequent builds are significantly faster — especially when dependencies have not changed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Container Security Fundamentals — Why It Matters
&lt;/h2&gt;

&lt;p&gt;Docker containers share the host kernel. This means — &lt;strong&gt;if a container escapes, an attacker can reach the host machine&lt;/strong&gt;. Do not treat security as an afterthought; you must think about it while writing your Dockerfile.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Container escape is real. In 2019, a runc vulnerability (CVE-2019-5736) allowed attackers to access the host machine from inside a container. In 2020, a Docker Desktop vulnerability followed. This is not theoretical — production systems have been compromised. Always follow security fundamentals.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Common Container Vulnerabilities
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Vulnerability&lt;/th&gt;
&lt;th&gt;Risk Level&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Running as root&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Container escape, host access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Outdated base images (CVEs)&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Known exploits in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Hardcoded secrets in image&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Credential theft via docker history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Privileged mode&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Full host access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Excessive capabilities&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Privilege escalation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;No resource limits&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Denial of Service (fork bomb)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Unscanned images&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Unknown vulnerabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Docker socket exposed&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;td&gt;Complete host compromise&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Real Attack Scenarios
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scenario 1: Secret leakage via docker history&lt;/span&gt;
docker &lt;span class="nb"&gt;history &lt;/span&gt;myapp:latest &lt;span class="nt"&gt;--no-trunc&lt;/span&gt;
&lt;span class="c"&gt;# IMAGE     CREATED   CREATED BY&lt;/span&gt;
&lt;span class="c"&gt;# abc123    ...       /bin/sh -c export API_KEY=sk-prod-secret123 &amp;amp;&amp;amp; node setup.js&lt;/span&gt;
&lt;span class="c"&gt;#                     ^^^^ VISIBLE TO ANYONE who pulls the image!&lt;/span&gt;

&lt;span class="c"&gt;# Scenario 2: Crypto mining via compromised public image&lt;/span&gt;
&lt;span class="c"&gt;# Attacker pushes malicious "ubuntu:latest" to a similarly-named registry&lt;/span&gt;
&lt;span class="c"&gt;# Your CI/CD pulls it, runs a crypto miner in your cloud (you pay the bill!)&lt;/span&gt;

&lt;span class="c"&gt;# Scenario 3: Docker socket exposure&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock myapp
&lt;span class="c"&gt;# Attacker inside container can now:&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; /:/host alpine &lt;span class="nb"&gt;chroot&lt;/span&gt; /host  &lt;span class="c"&gt;# Read/write host filesystem!&lt;/span&gt;

&lt;span class="c"&gt;# Scenario 4: Privileged container escape&lt;/span&gt;
docker run &lt;span class="nt"&gt;--privileged&lt;/span&gt; alpine
&lt;span class="c"&gt;# Inside container:&lt;/span&gt;
mount /dev/sda1 /mnt  &lt;span class="c"&gt;# Mount host disk&lt;/span&gt;
&lt;span class="nb"&gt;chroot&lt;/span&gt; /mnt           &lt;span class="c"&gt;# Root access to host!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Never mount &lt;code&gt;/var/run/docker.sock&lt;/code&gt; into a container in production unless absolutely necessary (such as for CI agents). Any container that can access the Docker socket effectively has root access to the entire machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Container security is layered: secure Dockerfile → runtime security flags → network policies → image scanning → secrets management → monitoring. If one layer fails, the next catches it. This is the "defense in depth" model.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Secure Dockerfile Practices — 20+ Best Practices
&lt;/h2&gt;

&lt;p&gt;Security starts at build time. An insecure Dockerfile cannot be fixed at runtime. Follow these best practices for production-ready Dockerfiles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete Secure Dockerfile Template
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ============================================&lt;/span&gt;
&lt;span class="c"&gt;# SECURE DOCKERFILE TEMPLATE — React/Node App&lt;/span&gt;
&lt;span class="c"&gt;# ============================================&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #1: Use specific versions — never "latest" or just "node"&lt;/span&gt;
&lt;span class="c"&gt;# The "latest" tag can change, breaking your build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18.19.0-alpine3.19&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #2: Use minimal base images&lt;/span&gt;
&lt;span class="c"&gt;# Security risk order (most to least): ubuntu &amp;gt; debian &amp;gt; debian-slim &amp;gt; alpine &amp;gt; distroless&lt;/span&gt;
&lt;span class="c"&gt;# Alpine has 98% fewer packages = smaller attack surface&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #3: Update packages in base image (apply security patches)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apk upgrade &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/cache/apk/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# Note: Alpine uses apk; Debian/Ubuntu uses apt-get&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #4: Create a non-root user EARLY&lt;/span&gt;
&lt;span class="c"&gt;# Never run the app as root!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 &lt;span class="nt"&gt;--ingroup&lt;/span&gt; appgroup appuser

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #5: Copy dependency files FIRST (caching + security)&lt;/span&gt;
&lt;span class="c"&gt;# Set proper ownership during COPY with --chown&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appgroup package*.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #6: Install ONLY production dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    npm cache clean &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;span class="c"&gt;# npm ci = exact versions from package-lock.json (more reproducible)&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #7: Copy app source with proper ownership&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appgroup . .&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #8: Build (if needed) while still root for permissions&lt;/span&gt;
&lt;span class="c"&gt;# Then switch to non-root for runtime&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Best Practice #9: Remove dev files and build artifacts&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; src/ tests/ &lt;span class="k"&gt;*&lt;/span&gt;.test.js .env.&lt;span class="k"&gt;*&lt;/span&gt; coverage/

&lt;span class="c"&gt;# Best Practice #10: Switch to non-root user — everything below runs as appuser&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #11: Expose ONLY required ports (documentation, not enforcement)&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="c"&gt;# Best Practice #12: Add HEALTHCHECK — K8s/Docker knows if app is actually working&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s --start-period=30s --retries=3 \&lt;/span&gt;
  CMD node -e "require('http').get('http://localhost:3000/health', r =&amp;gt; process.exit(r.statusCode===200?0:1))" || exit 1

&lt;span class="c"&gt;# Best Practice #13: Use exec form CMD (not shell form)&lt;/span&gt;
&lt;span class="c"&gt;# Shell form: CMD "node server.js" → /bin/sh -c "node server.js" (signals don't propagate!)&lt;/span&gt;
&lt;span class="c"&gt;# Exec form: CMD ["node", "server.js"] → direct process (signals work correctly)&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security Anti-Patterns — Never Do This
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ DANGEROUS: Hardcoded secrets visible in docker history!&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DATABASE_PASSWORD=mysupersecretpassword&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; AWS_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY&lt;/span&gt;

&lt;span class="c"&gt;# ❌ DANGEROUS: Even in RUN, it stays in layer history&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abc123secret &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run setup

&lt;span class="c"&gt;# ❌ BAD: Copying entire directory without .dockerignore&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="c"&gt;# This might include: .env, .git/, node_modules/, passwords.txt, etc.&lt;/span&gt;

&lt;span class="c"&gt;# ❌ BAD: Using latest tag&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:latest  # Which version? Will break unexpectedly!&lt;/span&gt;

&lt;span class="c"&gt;# ❌ BAD: Running as root (default if no USER instruction)&lt;/span&gt;
&lt;span class="c"&gt;# root inside container → if compromised, easier to escalate&lt;/span&gt;

&lt;span class="c"&gt;# ❌ BAD: Adding unnecessary packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl wget vim git build-essential
&lt;span class="c"&gt;# More packages = larger attack surface&lt;/span&gt;

&lt;span class="c"&gt;# ❌ BAD: Ignoring package-lock.json&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;  &lt;span class="c"&gt;# Might install different versions each time!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  .dockerignore — First Line of Defense
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Always maintain a comprehensive .dockerignore!

# Secrets and config
.env
.env.*
.env.local
.env.production
secrets/
*.pem
*.key
*.cert

# Development artifacts
node_modules/
npm-debug.log*
yarn-error.log

# Git history (can contain old secrets!)
.git/
.gitignore

# Tests and coverage (not needed in production image)
tests/
__tests__/
*.test.js
*.spec.js
coverage/
.nyc_output/

# Documentation
README.md
docs/
*.md

# IDE files
.vscode/
.idea/
*.swp

# CI/CD configs (not needed in image)
.github/
.gitlab-ci.yml
Jenkinsfile

# Build artifacts (will be regenerated)
dist/
build/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;code&gt;.env&lt;/code&gt; MUST be in &lt;code&gt;.dockerignore&lt;/code&gt;. If you use &lt;code&gt;COPY . .&lt;/code&gt; and &lt;code&gt;.env&lt;/code&gt; is not excluded, all your secrets end up in the image. Anyone can see them via &lt;code&gt;docker history myapp&lt;/code&gt; or image inspection. Real data breaches have happened this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Multi-stage builds give you both security and smaller image sizes. Build tools (gcc, make, npm, pip) do not belong in a production image. Copy only the runtime artifacts into the final stage — no build tools means a smaller attack surface and 60–80% smaller images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Google's distroless images contain no shell, no package manager, and no utilities — only the app runtime. An attacker cannot even open a shell. Use &lt;code&gt;gcr.io/distroless/nodejs18-debian11&lt;/code&gt;. The trade-off is harder debugging — use the debug variant for that purpose.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Runtime Security — Sandboxing Containers
&lt;/h2&gt;

&lt;p&gt;After building a secure Dockerfile, apply security flags at runtime. These flags keep the container in a tight sandbox — even if the container is compromised, the damage is contained.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime Security Flags
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Drop ALL capabilities, add back only what's needed&lt;/span&gt;
&lt;span class="c"&gt;# Linux capabilities are specific privileges that root has&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cap-drop&lt;/span&gt; ALL &lt;span class="nt"&gt;--cap-add&lt;/span&gt; NET_BIND_SERVICE nginx
&lt;span class="c"&gt;# Drops 38 capabilities and adds back only port binding &amp;lt; 1024&lt;/span&gt;

&lt;span class="c"&gt;# 2. Read-only filesystem — prevent tampering&lt;/span&gt;
docker run &lt;span class="nt"&gt;--read-only&lt;/span&gt; &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /var/run myapp
&lt;span class="c"&gt;# --read-only: container filesystem is read-only&lt;/span&gt;
&lt;span class="c"&gt;# --tmpfs /tmp: in-memory temp storage (data lost on restart)&lt;/span&gt;

&lt;span class="c"&gt;# 3. No new privileges — prevent sudo/setuid escalation&lt;/span&gt;
docker run &lt;span class="nt"&gt;--security-opt&lt;/span&gt; no-new-privileges myapp
&lt;span class="c"&gt;# Even if a setuid binary exists, it cannot escalate privileges&lt;/span&gt;

&lt;span class="c"&gt;# 4. Custom seccomp profile — limit system calls&lt;/span&gt;
docker run &lt;span class="nt"&gt;--security-opt&lt;/span&gt; &lt;span class="nv"&gt;seccomp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/seccomp-profile.json myapp
&lt;span class="c"&gt;# Default Docker seccomp blocks 44 of 300+ syscalls&lt;/span&gt;
&lt;span class="c"&gt;# A custom profile can be more restrictive&lt;/span&gt;

&lt;span class="c"&gt;# 5. AppArmor profile — Mandatory Access Control&lt;/span&gt;
docker run &lt;span class="nt"&gt;--security-opt&lt;/span&gt; &lt;span class="nv"&gt;apparmor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker-default myapp

&lt;span class="c"&gt;# 6. Resource limits — prevent Denial of Service&lt;/span&gt;
docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="se"&gt;\ &lt;/span&gt;          &lt;span class="c"&gt;# Max RAM&lt;/span&gt;
  &lt;span class="nt"&gt;--memory-swap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="se"&gt;\ &lt;/span&gt;     &lt;span class="c"&gt;# No swap (set equal to memory)&lt;/span&gt;
  &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 &lt;span class="se"&gt;\ &lt;/span&gt;             &lt;span class="c"&gt;# Max 0.5 CPU cores&lt;/span&gt;
  &lt;span class="nt"&gt;--pids-limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="se"&gt;\ &lt;/span&gt;       &lt;span class="c"&gt;# Max 100 processes (prevents fork bombs!)&lt;/span&gt;
  &lt;span class="nt"&gt;--ulimit&lt;/span&gt; &lt;span class="nv"&gt;nofile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024:1024 &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c"&gt;# Max open file descriptors&lt;/span&gt;
  myapp

&lt;span class="c"&gt;# 7. NEVER use --privileged in production!&lt;/span&gt;
&lt;span class="c"&gt;# docker run --privileged myapp  ← Gives container host-level access!&lt;/span&gt;

&lt;span class="c"&gt;# Complete secure run command&lt;/span&gt;
docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-drop&lt;/span&gt; ALL &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-add&lt;/span&gt; NET_BIND_SERVICE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--read-only&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-opt&lt;/span&gt; no-new-privileges &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pids-limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user&lt;/span&gt; 1001:1001 &lt;span class="se"&gt;\&lt;/span&gt;
  myapp:v1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Linux Capabilities — What to Grant and What to Deny
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Use in Production?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NET_BIND_SERVICE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bind ports &amp;lt; 1024 (80, 443)&lt;/td&gt;
&lt;td&gt;Only if port &amp;lt; 1024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NET_ADMIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Configure networking&lt;/td&gt;
&lt;td&gt;Only for network tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CHOWN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Change file ownership&lt;/td&gt;
&lt;td&gt;Very rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DAC_OVERRIDE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bypass file permissions&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FOWNER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bypass owner restrictions&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SYS_ADMIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Broad system admin access&lt;/td&gt;
&lt;td&gt;NEVER (equivalent to root!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SYS_PTRACE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Process tracing (debuggers)&lt;/td&gt;
&lt;td&gt;Never in production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;KILL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Send signals to processes&lt;/td&gt;
&lt;td&gt;Rarely needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  User Namespace Remapping (Advanced)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/etc/docker/daemon.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;remapping&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(UID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;becomes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;unprivileged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;host!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userns-remap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;After&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;systemctl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;restart&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;docker&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;UID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(non-privileged)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Never use &lt;code&gt;--privileged&lt;/code&gt; in production. The &lt;code&gt;--privileged&lt;/code&gt; flag effectively makes the container root on the host, bypassing the security context entirely. If any tutorial asks you to use &lt;code&gt;--privileged&lt;/code&gt; without explaining why, that tutorial is wrong. Use it only for specific, well-understood cases such as certain CI/CD systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Docker's default seccomp profile already blocks 44+ dangerous syscalls. To tighten further, create a custom profile. Docker's documentation provides the default profile as a starting point.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Image Scanning — Finding CVEs Before Production
&lt;/h2&gt;

&lt;p&gt;Your image is built and pushed — but does it contain known vulnerabilities? Image scanning tools automatically check for CVEs (Common Vulnerabilities and Exposures) in your base image and dependencies.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A CVE (Common Vulnerability and Exposure) is the unique identifier for a publicly known security vulnerability. For example, &lt;code&gt;CVE-2021-44228&lt;/code&gt; was the Log4Shell vulnerability. A scanner compares your image's packages against a CVE database.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Trivy — Most Popular Free Scanner
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Trivy (Linux/macOS)&lt;/span&gt;
curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | &lt;span class="se"&gt;\&lt;/span&gt;
  sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; /usr/local/bin

&lt;span class="c"&gt;# macOS via Homebrew&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;trivy

&lt;span class="c"&gt;# Basic image scan&lt;/span&gt;
trivy image nginx:latest

&lt;span class="c"&gt;# Scan with severity filter — only show HIGH and CRITICAL&lt;/span&gt;
trivy image &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH,CRITICAL nginx:latest

&lt;span class="c"&gt;# Fail CI/CD if CRITICAL vulnerabilities found (exit code 1)&lt;/span&gt;
trivy image &lt;span class="nt"&gt;--exit-code&lt;/span&gt; 1 &lt;span class="nt"&gt;--severity&lt;/span&gt; CRITICAL myapp:latest

&lt;span class="c"&gt;# Scan your local Dockerfile for misconfigurations&lt;/span&gt;
trivy config ./Dockerfile

&lt;span class="c"&gt;# Scan docker-compose.yml for security issues&lt;/span&gt;
trivy config ./docker-compose.yml

&lt;span class="c"&gt;# Output as JSON for automated processing&lt;/span&gt;
trivy image &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="nt"&gt;--output&lt;/span&gt; results.json myapp:latest

&lt;span class="c"&gt;# Scan a GitHub repository (without cloning)&lt;/span&gt;
trivy repo https://github.com/user/myrepo

&lt;span class="c"&gt;# Generate SBOM (Software Bill of Materials)&lt;/span&gt;
trivy image &lt;span class="nt"&gt;--format&lt;/span&gt; cyclonedx &lt;span class="nt"&gt;--output&lt;/span&gt; sbom.json myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions — Trivy scan in CI/CD&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Trivy vulnerability scanner&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aquasecurity/trivy-action@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:latest&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sarif&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trivy-results.sarif&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CRITICAL,HIGH&lt;/span&gt;
    &lt;span class="na"&gt;exit-code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Fail build on critical issues&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Trivy results to GitHub Security tab&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trivy-results.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Scout — Built-in Scanner
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Docker Scout (built-in since Docker Desktop 4.17)&lt;/span&gt;

&lt;span class="c"&gt;# Quick vulnerability overview&lt;/span&gt;
docker scout quickview myapp:latest

&lt;span class="c"&gt;# Detailed CVE list&lt;/span&gt;
docker scout cves myapp:latest

&lt;span class="c"&gt;# Filter by severity&lt;/span&gt;
docker scout cves &lt;span class="nt"&gt;--only-severity&lt;/span&gt; critical myapp:latest

&lt;span class="c"&gt;# Compare two versions (what changed?)&lt;/span&gt;
docker scout compare &lt;span class="nt"&gt;--to&lt;/span&gt; myapp:v1.0 myapp:v2.0

&lt;span class="c"&gt;# Get recommendations (what base image to use?)&lt;/span&gt;
docker scout recommendations myapp:latest

&lt;span class="c"&gt;# Local filesystem scan&lt;/span&gt;
docker scout cves fs://./  &lt;span class="c"&gt;# Scan current directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Snyk — Enterprise Scanner
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Snyk CLI&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; snyk

&lt;span class="c"&gt;# Authenticate (free account at snyk.io)&lt;/span&gt;
snyk auth

&lt;span class="c"&gt;# Scan Docker image&lt;/span&gt;
snyk container &lt;span class="nb"&gt;test &lt;/span&gt;nginx:latest

&lt;span class="c"&gt;# Scan with fail threshold&lt;/span&gt;
snyk container &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--severity-threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;high nginx:latest

&lt;span class="c"&gt;# Monitor over time (track vulnerabilities as they are discovered)&lt;/span&gt;
snyk container monitor nginx:latest

&lt;span class="c"&gt;# Snyk in CI/CD (GitHub Actions)&lt;/span&gt;
- name: Run Snyk to check Docker image &lt;span class="k"&gt;for &lt;/span&gt;vulnerabilities
  uses: snyk/actions/docker@master
  &lt;span class="nb"&gt;env&lt;/span&gt;:
    SNYK_TOKEN: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;{ secrets.SNYK_TOKEN &lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
  with:
    image: myapp:latest
    args: &lt;span class="nt"&gt;--severity-threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For development: use Docker Scout (built-in, no setup). For CI/CD: use Trivy (free, fast, no registration). For enterprise: use Snyk or Aqua Security (paid, more features). ECR users: enable "Scan on Push" in repository settings — it is automatic and free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Achieving zero vulnerabilities is an unrealistic goal. Having LOW/MEDIUM vulnerabilities in production images is normal — fixing everything is not feasible. Focus on: always fix CRITICAL, fix or document exceptions for HIGH, address MEDIUM only if exploitable. False positives also occur — triage carefully.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Secrets Management — Never Bake Secrets Into Images
&lt;/h2&gt;

&lt;p&gt;The most common mistake in container security is hardcoding secrets (passwords, API keys, tokens) into Docker images. Once an image is pushed to a registry, &lt;strong&gt;anyone who pulls it can inspect the secrets&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;code&gt;docker history&lt;/code&gt; is a secret leakage tool. Every Dockerfile instruction creates an image layer. &lt;code&gt;ENV API_KEY=secret&lt;/code&gt; is permanently baked into that layer — even if you unset it later. Anyone can see it via &lt;code&gt;docker history --no-trunc myapp&lt;/code&gt;. Real incident: a company put API keys in their Dockerfile, pushed the image as public to Docker Hub, and received a $50,000 AWS bill from crypto mining.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Never Do This
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ EXTREMELY DANGEROUS — visible in docker history forever!&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DATABASE_PASSWORD=mysupersecretpassword123&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JWT_SECRET=my-jwt-secret-key&lt;/span&gt;

&lt;span class="c"&gt;# ❌ ALSO DANGEROUS — even in RUN, it stays in the layer!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-prod-abc123 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./configure.sh

&lt;span class="c"&gt;# ❌ BAD — copying .env file into image&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; .env /app/.env  # .env is now inside the image layers!&lt;/span&gt;

&lt;span class="c"&gt;# Check what's visible (run this on your image)&lt;/span&gt;
docker history --no-trunc myapp | grep -i "password\|secret\|key\|token"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Approach 1: Runtime Environment Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pass at runtime — not baked into the image&lt;/span&gt;
docker run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret myapp

&lt;span class="c"&gt;# From a file — more manageable (file security is your responsibility)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--env-file&lt;/span&gt; secrets.env myapp

&lt;span class="c"&gt;# secrets.env format (add this file to .gitignore!)&lt;/span&gt;
&lt;span class="c"&gt;# DATABASE_URL=postgresql://user:pass@host:5432/db&lt;/span&gt;
&lt;span class="c"&gt;# API_KEY=sk-prod-abc123&lt;/span&gt;
&lt;span class="c"&gt;# JWT_SECRET=super-secret-jwt-key&lt;/span&gt;

&lt;span class="c"&gt;# CAVEAT: Still visible in 'docker inspect'!&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].Config.Env'&lt;/span&gt;
&lt;span class="c"&gt;# [&lt;/span&gt;
&lt;span class="c"&gt;#   "DATABASE_PASSWORD=secret",  ← Visible to anyone with docker inspect!&lt;/span&gt;
&lt;span class="c"&gt;#   "PATH=/usr/local/sbin:..."&lt;/span&gt;
&lt;span class="c"&gt;# ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Approach 2: Docker Secrets (Swarm Mode)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a Docker secret (stored encrypted in Swarm Raft)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"mysupersecretpassword"&lt;/span&gt; | docker secret create db_password -
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"sk-prod-abc123secret"&lt;/span&gt; | docker secret create api_key -

&lt;span class="c"&gt;# List secrets (values NOT shown)&lt;/span&gt;
docker secret &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# Use in service (mounted as file at /run/secrets/secretname)&lt;/span&gt;
docker service create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret&lt;/span&gt; db_password &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret&lt;/span&gt; api_key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;DB_PASSWORD_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/run/secrets/db_password &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;API_KEY_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/run/secrets/api_key &lt;span class="se"&gt;\&lt;/span&gt;
  myapp

&lt;span class="c"&gt;# Inside container, secrets are files:&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /run/secrets/db_password  &lt;span class="c"&gt;# mysupersecretpassword&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /run/secrets/api_key      &lt;span class="c"&gt;# sk-prod-abc123secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml with secrets (Swarm mode)&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;

&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;    &lt;span class="c1"&gt;# Secret already created via docker secret create&lt;/span&gt;
  &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:latest&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_password&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api_key&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Point to secret files, not actual values&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_PASSWORD_FILE=/run/secrets/db_password&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;API_KEY_FILE=/run/secrets/api_key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Approach 3: AWS Secrets Manager (Production Best Practice)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# entrypoint.sh — Fetch secrets at startup from AWS Secrets Manager&lt;/span&gt;
&lt;span class="c"&gt;# Container uses an IAM role (no hardcoded AWS credentials needed!)&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Fetching secrets from AWS Secrets Manager..."&lt;/span&gt;

&lt;span class="c"&gt;# Fetch database password&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws secretsmanager get-secret-value &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; prod/myapp/db_password &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; SecretString &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Fetch API key&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws secretsmanager get-secret-value &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-id&lt;/span&gt; prod/myapp/api_key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; SecretString &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Secrets loaded successfully"&lt;/span&gt;

&lt;span class="c"&gt;# Execute the main command (e.g., node server.js)&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Approach 4: BuildKit Secret Mounts (Build-time Secrets)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Secret mounted ONLY during this RUN — not stored in the image layer!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret,id&lt;span class="o"&gt;=&lt;/span&gt;npmrc,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npmrc &lt;span class="se"&gt;\
&lt;/span&gt;    npm &lt;span class="nb"&gt;install&lt;/span&gt;  &lt;span class="c"&gt;# Can access private npm registry using .npmrc secret&lt;/span&gt;

&lt;span class="c"&gt;# Private repo clone during build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ssh &lt;span class="se"&gt;\
&lt;/span&gt;    git clone git@github.com:myorg/private-repo.git /app/lib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build with secret (secret never enters the image!)&lt;/span&gt;
docker build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;npmrc,src&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.npmrc &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# SSH forwarding for private repos&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;ssh-agent&lt;span class="si"&gt;)&lt;/span&gt;
ssh-add ~/.ssh/id_rsa
docker build &lt;span class="nt"&gt;--ssh&lt;/span&gt; default &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always add &lt;code&gt;.env&lt;/code&gt; to both &lt;code&gt;.gitignore&lt;/code&gt; and &lt;code&gt;.dockerignore&lt;/code&gt;. Commit a &lt;code&gt;.env.example&lt;/code&gt; template without values so team members know which variables are required. In production, never use &lt;code&gt;.env&lt;/code&gt; files — use a proper secrets management solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Secrets manager options include: AWS Secrets Manager (paid, excellent rotation support), HashiCorp Vault (free, powerful, complex), Kubernetes Secrets (base64-encoded — not truly secret!), Doppler (developer-friendly SaaS), Azure Key Vault, and GCP Secret Manager. Choose based on your cloud provider.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  BuildKit — Modern Docker Build Engine
&lt;/h2&gt;

&lt;p&gt;BuildKit is the default build engine in Docker 23+. It is &lt;strong&gt;much faster, safer, and more feature-rich&lt;/strong&gt; than the older builder. Parallel builds, better caching, secret mounts — all of these come from BuildKit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Old builder: sequential layers, no cache import/export, no secret mounts. BuildKit: parallel stage builds, registry cache, secret mounts, SSH forwarding, multi-arch support. Master BuildKit once and CI/CD build times improve dramatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Enabling BuildKit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if BuildKit is available&lt;/span&gt;
docker buildx version
&lt;span class="c"&gt;# github.com/docker/buildx v0.12.0 docker-desktop&lt;/span&gt;

&lt;span class="c"&gt;# Docker 23+ enables it automatically&lt;/span&gt;
&lt;span class="c"&gt;# For older versions:&lt;/span&gt;

&lt;span class="c"&gt;# Option 1: Per-build environment variable&lt;/span&gt;
&lt;span class="nv"&gt;DOCKER_BUILDKIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Option 2: Enable permanently in daemon.json&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/docker/daemon.json
&lt;span class="c"&gt;# Add: { "features": { "buildkit": true } }&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart docker

&lt;span class="c"&gt;# Option 3: Set DOCKER_BUILDKIT in shell profile&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export DOCKER_BUILDKIT=1'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  BuildKit Special Features — syntax=docker/dockerfile:1
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# This comment enables the latest BuildKit Dockerfile syntax&lt;/span&gt;
&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Feature 1: Secret mounts — never stored in image layers!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret,id&lt;span class="o"&gt;=&lt;/span&gt;npmrc,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npmrc &lt;span class="se"&gt;\
&lt;/span&gt;    npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--legacy-peer-deps&lt;/span&gt;

&lt;span class="c"&gt;# Feature 2: Cache mounts — persist between builds!&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.npm &lt;span class="se"&gt;\
&lt;/span&gt;    npm ci  &lt;span class="c"&gt;# npm cache persists across builds = MUCH faster!&lt;/span&gt;

&lt;span class="c"&gt;# Feature 3: Bind mounts — mount host directory without COPY&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package.json,target&lt;span class="o"&gt;=&lt;/span&gt;package.json &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json,target&lt;span class="o"&gt;=&lt;/span&gt;package-lock.json &lt;span class="se"&gt;\
&lt;/span&gt;    npm ci

&lt;span class="c"&gt;# Feature 4: SSH forwarding&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ssh &lt;span class="se"&gt;\
&lt;/span&gt;    git clone git@github.com:private/repo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker buildx Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all builders&lt;/span&gt;
docker buildx &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# Create a new builder with docker-container driver (more features)&lt;/span&gt;
docker buildx create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; mybuilder &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; docker-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bootstrap&lt;/span&gt;
docker buildx use mybuilder

&lt;span class="c"&gt;# Inspect builder (shows supported platforms)&lt;/span&gt;
docker buildx inspect mybuilder

&lt;span class="c"&gt;# Build for current platform (like regular docker build)&lt;/span&gt;
docker buildx build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Build for a specific platform&lt;/span&gt;
docker buildx build &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64 &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:amd64 &lt;span class="nt"&gt;--load&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
docker buildx build &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/arm64 &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:arm64 &lt;span class="nt"&gt;--load&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Build and push multi-arch image in one command!&lt;/span&gt;
docker buildx build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64,linux/arm64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; username/myapp:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--push&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Remove builder&lt;/span&gt;
docker buildx &lt;span class="nb"&gt;rm &lt;/span&gt;mybuilder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Registry Cache — CI/CD Game Changer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Export cache to registry (persists between CI runs)&lt;/span&gt;
docker buildx build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cache-to&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry,ref&lt;span class="o"&gt;=&lt;/span&gt;myapp:buildcache,mode&lt;span class="o"&gt;=&lt;/span&gt;max &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cache-from&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry,ref&lt;span class="o"&gt;=&lt;/span&gt;myapp:buildcache &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--push&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="c"&gt;# mode=max: Cache all intermediate layers (maximum cache hits)&lt;/span&gt;
&lt;span class="c"&gt;# mode=min: Cache only final layer (smaller cache size)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions — Optimal BuildKit caching&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push with cache&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:latest&lt;/span&gt;
    &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;          &lt;span class="c1"&gt;# GitHub Actions cache (free!)&lt;/span&gt;
    &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;   &lt;span class="c1"&gt;# Save cache for next run&lt;/span&gt;
    &lt;span class="c1"&gt;# Result: 5-minute builds → 45-second builds on cache hit!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Without caching: a Node app build takes 4–5 minutes. With caching (dependencies unchanged): 30–60 seconds. For large enterprise apps: 20 minutes → 2 minutes. This difference directly affects production deployment frequency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; With multi-arch builds, &lt;code&gt;--load&lt;/code&gt; (local image) and &lt;code&gt;--push&lt;/code&gt; (registry) cannot be used simultaneously in most cases. For single-arch builds use &lt;code&gt;--load&lt;/code&gt;; for multi-arch, push directly to the registry with &lt;code&gt;--push&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Multi-Architecture Images — Supporting ARM and x86
&lt;/h2&gt;

&lt;p&gt;You develop on a MacBook M1/M2/M3 (ARM64) and deploy to AWS EC2 (amd64). Multi-arch images work on both platforms without issues — no need for separate Dockerfiles.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Apple Silicon Macs (ARM64) have become standard dev machines. AWS Graviton instances (ARM64) offer the same performance at 40% lower cost than x86. If your image only supports amd64, ARM machines will produce a "wrong platform" error. Build multi-arch images to support everyone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setup and Build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Install QEMU (enables emulation of other architectures)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--privileged&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; tonistiigi/binfmt &lt;span class="nt"&gt;--install&lt;/span&gt; all
&lt;span class="c"&gt;# Installs QEMU user-space emulators for ARM, etc.&lt;/span&gt;

&lt;span class="c"&gt;# Verify QEMU installation&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; /proc/sys/fs/binfmt_misc/
&lt;span class="c"&gt;# Should show: qemu-aarch64, qemu-arm, etc.&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Create a multi-arch capable builder&lt;/span&gt;
docker buildx create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; multiarch-builder &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; docker-container &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bootstrap&lt;/span&gt;
docker buildx use multiarch-builder

&lt;span class="c"&gt;# Step 3: Inspect — verify which platforms are supported&lt;/span&gt;
docker buildx inspect multiarch-builder &lt;span class="nt"&gt;--bootstrap&lt;/span&gt;
&lt;span class="c"&gt;# Platforms: linux/amd64, linux/arm64, linux/arm/v7, linux/386...&lt;/span&gt;

&lt;span class="c"&gt;# Step 4: Build and push multi-arch image in one command&lt;/span&gt;
docker buildx build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64,linux/arm64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag&lt;/span&gt; username/myapp:latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag&lt;/span&gt; username/myapp:v1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--push&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Step 5: Verify multi-arch manifest&lt;/span&gt;
docker manifest inspect username/myapp:latest
&lt;span class="c"&gt;# Shows separate manifests for each architecture&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Platform-Specific Handling in Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;

&lt;span class="c"&gt;# BuildKit injects these automatically&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; TARGETPLATFORM&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; TARGETARCH&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; TARGETOS&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;

&lt;span class="c"&gt;# Platform-specific commands (if needed)&lt;/span&gt;
&lt;span class="k"&gt;RUN case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGETARCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    amd64&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building for x86_64"&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    arm64&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building for ARM64"&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    arm&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building for ARM v7"&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="k"&gt;esac&lt;/span&gt;

&lt;span class="c"&gt;# Most Node.js apps need no platform-specific code&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Actions — Multi-Arch CI/CD
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Multi-Arch Build&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up QEMU (ARM emulation)&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-qemu-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Docker Hub&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKER_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKER_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push multi-arch&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;username/myapp:latest&lt;/span&gt;
          &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;
          &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; QEMU emulation makes ARM64 builds 5–10x slower on an amd64 machine. Solutions: use native ARM builders (GitHub has paid ARM64 runners), or run matrix builds (amd64 and arm64 in parallel). QEMU is fine for small images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; AWS Graviton3 (arm64) instances are 20–40% cheaper than equivalent x86 instances for the same workload. Build one multi-arch image, deploy on Graviton — significant cost savings at scale. t4g instances are cheaper than t3, c7g instances are cheaper than c6i.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Context — Managing Multiple Docker Hosts
&lt;/h2&gt;

&lt;p&gt;Docker contexts let you manage multiple Docker hosts from one machine — local, staging server, production server — all with the same &lt;code&gt;docker&lt;/code&gt; command. Switch context and all commands go there.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Contexts are useful when you want to run Docker commands on remote servers without SSH-ing in manually, or when managing multiple environments (dev, staging, production) from a CI/CD workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Context Management Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all contexts (* = currently active)&lt;/span&gt;
docker context &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="c"&gt;# NAME           DESCRIPTION   DOCKER ENDPOINT               KUBERNETES ENDPOINT&lt;/span&gt;
&lt;span class="c"&gt;# default *      ...           unix:///var/run/docker.sock&lt;/span&gt;
&lt;span class="c"&gt;# remote-server  ...           ssh://deploy@203.0.113.10&lt;/span&gt;

&lt;span class="c"&gt;# Create context for a remote server (uses SSH)&lt;/span&gt;
docker context create remote-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Production server"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker&lt;/span&gt; &lt;span class="s2"&gt;"host=ssh://deploy@203.0.113.10"&lt;/span&gt;

&lt;span class="c"&gt;# Create context with a specific SSH key&lt;/span&gt;
docker context create staging &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--docker&lt;/span&gt; &lt;span class="s2"&gt;"host=ssh://ubuntu@staging.myapp.com"&lt;/span&gt;

&lt;span class="c"&gt;# Switch to remote context&lt;/span&gt;
docker context use remote-server
&lt;span class="c"&gt;# All docker commands now go to the remote host!&lt;/span&gt;

docker ps      &lt;span class="c"&gt;# Shows containers on remote server!&lt;/span&gt;
docker images  &lt;span class="c"&gt;# Shows images on remote server!&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; nginx  &lt;span class="c"&gt;# Runs on remote server!&lt;/span&gt;

&lt;span class="c"&gt;# Switch back to local&lt;/span&gt;
docker context use default

&lt;span class="c"&gt;# One-off command in a specific context (without switching)&lt;/span&gt;
docker &lt;span class="nt"&gt;--context&lt;/span&gt; remote-server ps
docker &lt;span class="nt"&gt;--context&lt;/span&gt; remote-server logs myapp &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Inspect a context&lt;/span&gt;
docker context inspect remote-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Export/Import Contexts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Export context (share with team)&lt;/span&gt;
docker context &lt;span class="nb"&gt;export &lt;/span&gt;remote-server &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; production.dockercontext

&lt;span class="c"&gt;# Import on another machine&lt;/span&gt;
docker context import production-server production.dockercontext

&lt;span class="c"&gt;# Remove a context&lt;/span&gt;
docker context &lt;span class="nb"&gt;rm &lt;/span&gt;old-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Context with AWS ECS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create ECS context (deploy directly to ECS!)&lt;/span&gt;
docker context create ecs myecscontext
&lt;span class="c"&gt;# Prompts for AWS credentials/profile&lt;/span&gt;

&lt;span class="c"&gt;# Switch to ECS context&lt;/span&gt;
docker context use myecscontext

&lt;span class="c"&gt;# Now docker compose commands deploy to ECS!&lt;/span&gt;
docker compose up  &lt;span class="c"&gt;# Deploys to ECS Fargate!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Define hosts in &lt;code&gt;~/.ssh/config&lt;/code&gt;, then reference them in a context: &lt;code&gt;docker context create prod --docker "host=ssh://prod-server"&lt;/code&gt;. SSH config can include jump hosts and identity files — complex infrastructure can be managed cleanly this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; A common mistake is switching to a remote context, doing some work, and forgetting about it. The next local command runs on the remote host instead. Always check the current context with &lt;code&gt;docker context ls&lt;/code&gt;. Consider showing the current context in your shell prompt using Starship or oh-my-zsh plugins.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Plugins — Extending Functionality
&lt;/h2&gt;

&lt;p&gt;Docker plugins extend Docker daemon capabilities with custom volume drivers, network drivers, and authorization plugins. They are useful in enterprise environments and for integrating cloud storage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Plugin types — Volume plugins: connect cloud storage (AWS EBS, NFS, GlusterFS) to Docker volumes. Network plugins: custom networking (Weave, Calico, Flannel). Authorization plugins: centralized access control defining what each container is allowed to do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Volume Plugins
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List installed plugins&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# Install NFS volume plugin (mount NFS shares as Docker volumes)&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;install &lt;/span&gt;vieux/sshfs &lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1

&lt;span class="c"&gt;# Install local-persist plugin (volumes survive container removal)&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;install &lt;/span&gt;cwspear/docker-local-persist-volume-plugin

&lt;span class="c"&gt;# Enable/disable plugin&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;enable &lt;/span&gt;pluginname
docker plugin disable pluginname

&lt;span class="c"&gt;# Remove plugin&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;rm &lt;/span&gt;pluginname

&lt;span class="c"&gt;# Inspect plugin&lt;/span&gt;
docker plugin inspect vieux/sshfs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# SSHFS Plugin — Mount a remote directory as a Docker volume&lt;/span&gt;
&lt;span class="c"&gt;# Useful for: shared config files, remote storage&lt;/span&gt;

&lt;span class="c"&gt;# First install the plugin&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;install &lt;/span&gt;vieux/sshfs

&lt;span class="c"&gt;# Create volume using plugin&lt;/span&gt;
docker volume create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; vieux/sshfs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nv"&gt;sshcmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;user@remote-host:/path/to/dir &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysshpassword &lt;span class="se"&gt;\&lt;/span&gt;
  remote-data

&lt;span class="c"&gt;# Use volume in container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; remote-data:/app/data myapp

&lt;span class="c"&gt;# AWS EBS volume plugin (for persistent EBS volumes)&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;install &lt;/span&gt;rexray/ebs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;EBS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ap-south-1

docker volume create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--driver&lt;/span&gt; rexray/ebs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--opt&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20 &lt;span class="se"&gt;\&lt;/span&gt;
  my-ebs-volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Authorization Plugins — Enterprise Security
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# OPA (Open Policy Agent) authorization plugin&lt;/span&gt;
&lt;span class="c"&gt;# Define policies: what each container is allowed to do&lt;/span&gt;

&lt;span class="c"&gt;# Install&lt;/span&gt;
docker plugin &lt;span class="nb"&gt;install &lt;/span&gt;openpolicyagent/opa-docker-authz-local:v0.2 &lt;span class="se"&gt;\&lt;/span&gt;
  policy-file&lt;span class="o"&gt;=&lt;/span&gt;/etc/docker/policies/authz.rego

&lt;span class="c"&gt;# authz.rego policy example (Rego language)&lt;/span&gt;
&lt;span class="c"&gt;# package docker.authz&lt;/span&gt;
&lt;span class="c"&gt;# allow {&lt;/span&gt;
&lt;span class="c"&gt;#   not deny&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;
&lt;span class="c"&gt;# deny {&lt;/span&gt;
&lt;span class="c"&gt;#   input.Body.HostConfig.Privileged == true  # Block privileged containers&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;

&lt;span class="c"&gt;# Enable in daemon.json:&lt;/span&gt;
&lt;span class="c"&gt;# {&lt;/span&gt;
&lt;span class="c"&gt;#   "authorization-plugins": ["openpolicyagent/opa-docker-authz-local:v0.2"]&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Docker plugins run with daemon-level access. Only use trusted, well-maintained plugins. Review permissions carefully when installing — a malicious plugin can compromise the entire Docker daemon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Many plugin use cases are now better handled by Kubernetes and CSI (Container Storage Interface) drivers. If you are using Kubernetes, explore CSI drivers instead of Docker plugins — the ecosystem is better and offers more options.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Resource Management — Limiting Container Resources
&lt;/h2&gt;

&lt;p&gt;Without resource limits, a misbehaving container can consume all the memory and CPU on the host, crashing everything else. Apply limits to every container in production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;code&gt;:(){ :|:&amp;amp; };:&lt;/code&gt; is a bash fork bomb. Without &lt;code&gt;--pids-limit&lt;/code&gt;, this can crash an entire machine from a single container. In the real world, buggy code leaks memory or spawns infinite processes. Always set limits.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Memory Limits
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Hard memory limit — OOM killer triggers if exceeded&lt;/span&gt;
docker run &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m myapp

&lt;span class="c"&gt;# Memory + swap limit&lt;/span&gt;
docker run &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="nt"&gt;--memory-swap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1g myapp
&lt;span class="c"&gt;# RAM: 512MB, Swap: 512MB (total 1GB, swap = total - ram)&lt;/span&gt;

&lt;span class="c"&gt;# Disable swap for container&lt;/span&gt;
docker run &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m &lt;span class="nt"&gt;--memory-swap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512m myapp

&lt;span class="c"&gt;# Soft limit — suggestion, not enforced (for low-memory situations)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--memory-reservation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;256m myapp

&lt;span class="c"&gt;# OOM kill disable (risky — lets container grow without being killed)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--oom-kill-disable&lt;/span&gt; myapp  &lt;span class="c"&gt;# Use with extreme caution!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CPU Limits
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Hard CPU limit — cannot exceed X cores&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.5 myapp       &lt;span class="c"&gt;# Max 1.5 CPU cores&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.25 myapp      &lt;span class="c"&gt;# Max 25% of one CPU core&lt;/span&gt;

&lt;span class="c"&gt;# CPU shares — relative weight (soft limit, when contention exists)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cpu-shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512 myapp  &lt;span class="c"&gt;# Default is 1024; gets half the CPU when contested&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cpu-shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2048 myapp &lt;span class="c"&gt;# Gets double the default CPU priority&lt;/span&gt;

&lt;span class="c"&gt;# Pin to specific CPU cores (NUMA optimization, isolation)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cpuset-cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0,1 myapp  &lt;span class="c"&gt;# Only use CPUs 0 and 1&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cpuset-cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2-5 myapp  &lt;span class="c"&gt;# Use CPUs 2, 3, 4, 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  I/O and Other Limits
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Disk I/O limits&lt;/span&gt;
docker run &lt;span class="nt"&gt;--device-read-bps&lt;/span&gt; /dev/sda:100mb myapp    &lt;span class="c"&gt;# Max 100MB/s read&lt;/span&gt;
docker run &lt;span class="nt"&gt;--device-write-bps&lt;/span&gt; /dev/sda:50mb myapp    &lt;span class="c"&gt;# Max 50MB/s write&lt;/span&gt;
docker run &lt;span class="nt"&gt;--device-read-iops&lt;/span&gt; /dev/sda:1000 myapp    &lt;span class="c"&gt;# Max 1000 IOPS read&lt;/span&gt;

&lt;span class="c"&gt;# PID limit (prevent fork bombs!)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--pids-limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 myapp  &lt;span class="c"&gt;# Max 100 processes/threads&lt;/span&gt;

&lt;span class="c"&gt;# Open file descriptor limit&lt;/span&gt;
docker run &lt;span class="nt"&gt;--ulimit&lt;/span&gt; &lt;span class="nv"&gt;nofile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024:1024 myapp
&lt;span class="c"&gt;# Format: soft_limit:hard_limit&lt;/span&gt;

&lt;span class="c"&gt;# Update limits on a RUNNING container (no restart needed!)&lt;/span&gt;
docker update &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1g &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 myapp

&lt;span class="c"&gt;# Check resource usage (real-time)&lt;/span&gt;
docker stats                     &lt;span class="c"&gt;# All containers&lt;/span&gt;
docker stats myapp               &lt;span class="c"&gt;# Specific container&lt;/span&gt;
docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt; myapp   &lt;span class="c"&gt;# Snapshot, not live&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Production Resource Sizing Guide
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service Type&lt;/th&gt;
&lt;th&gt;CPU&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web API (Node.js)&lt;/td&gt;
&lt;td&gt;0.5–1.0&lt;/td&gt;
&lt;td&gt;256–512MB&lt;/td&gt;
&lt;td&gt;Scale horizontally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database (PostgreSQL)&lt;/td&gt;
&lt;td&gt;2.0+&lt;/td&gt;
&lt;td&gt;1–4GB&lt;/td&gt;
&lt;td&gt;Depends on dataset size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache (Redis)&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;256–512MB&lt;/td&gt;
&lt;td&gt;Set maxmemory in Redis config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background Worker&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;512MB–1GB&lt;/td&gt;
&lt;td&gt;Depends on task type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message Queue&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;512MB&lt;/td&gt;
&lt;td&gt;RabbitMQ/Kafka varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring Agent&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;64–128MB&lt;/td&gt;
&lt;td&gt;Should be minimal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nginx Proxy&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;128MB&lt;/td&gt;
&lt;td&gt;Very lightweight&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml with resource limits&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapi:latest&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.5"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
        &lt;span class="na"&gt;reservations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.25"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256M&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2.0"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2G&lt;/span&gt;
        &lt;span class="na"&gt;reservations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Strategy for setting limits: first run without limits and observe actual usage with &lt;code&gt;docker stats&lt;/code&gt;. Then set limit = actual_peak × 1.5 (with buffer). Test on staging to verify the app works correctly within limits. Deploy to production and track OOM kills and CPU throttling via monitoring.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Debugging Containers — When Nothing Works
&lt;/h2&gt;

&lt;p&gt;"It works on my machine" — everyone has heard this. Containers have their own issues. Here is a systematic debugging methodology for quickly resolving production problems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Debugging order: Status → Logs → Resources → Exec into container → Processes → Network → Events. Follow this order — 80% of issues are resolved via logs or exec. Advanced tools are only needed when the basics fail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Systematic Debugging Playbook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ===== STEP 1: Container Status =====&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;  &lt;span class="c"&gt;# -a shows stopped containers too&lt;/span&gt;
&lt;span class="c"&gt;# STATUS: Up 2 hours (good) | Exited (1) 5 min ago (bad) | Restarting (crash loop!)&lt;/span&gt;

&lt;span class="c"&gt;# Detailed status&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].State'&lt;/span&gt;
&lt;span class="c"&gt;# {&lt;/span&gt;
&lt;span class="c"&gt;#   "Status": "exited",&lt;/span&gt;
&lt;span class="c"&gt;#   "Running": false,&lt;/span&gt;
&lt;span class="c"&gt;#   "ExitCode": 137,  ← 137 = OOM kill! 1 = app error, 0 = clean exit&lt;/span&gt;
&lt;span class="c"&gt;#   "OOMKilled": true,&lt;/span&gt;
&lt;span class="c"&gt;#   "Error": ""&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;

&lt;span class="c"&gt;# ===== STEP 2: Logs =====&lt;/span&gt;
docker logs myapp              &lt;span class="c"&gt;# All logs&lt;/span&gt;
docker logs myapp &lt;span class="nt"&gt;--tail&lt;/span&gt; 100   &lt;span class="c"&gt;# Last 100 lines&lt;/span&gt;
docker logs myapp &lt;span class="nt"&gt;-f&lt;/span&gt;           &lt;span class="c"&gt;# Follow (like tail -f)&lt;/span&gt;
docker logs myapp &lt;span class="nt"&gt;--since&lt;/span&gt; 30m  &lt;span class="c"&gt;# Last 30 minutes&lt;/span&gt;
docker logs myapp 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; error  &lt;span class="c"&gt;# Filter errors&lt;/span&gt;

&lt;span class="c"&gt;# ===== STEP 3: Resource Usage =====&lt;/span&gt;
docker stats myapp &lt;span class="nt"&gt;--no-stream&lt;/span&gt;
&lt;span class="c"&gt;# CONTAINER  CPU %  MEM USAGE / LIMIT   MEM %   NET I/O    BLOCK I/O&lt;/span&gt;
&lt;span class="c"&gt;# myapp      95.2%  490MiB / 512MiB    95.7%   1.2GB/500MB  0B/0B&lt;/span&gt;
&lt;span class="c"&gt;# ← 95% memory → OOM kill is imminent!&lt;/span&gt;

&lt;span class="c"&gt;# ===== STEP 4: Shell into Running Container =====&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp bash    &lt;span class="c"&gt;# bash shell (Debian/Ubuntu)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp sh      &lt;span class="c"&gt;# sh shell (Alpine)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp /bin/sh &lt;span class="c"&gt;# explicit path&lt;/span&gt;

&lt;span class="c"&gt;# Run a specific command without interactive shell&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp &lt;span class="nb"&gt;env&lt;/span&gt;          &lt;span class="c"&gt;# Check environment variables&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp &lt;span class="nb"&gt;cat&lt;/span&gt; /app/config.json

&lt;span class="c"&gt;# ===== STEP 5: Check Processes =====&lt;/span&gt;
docker top myapp               &lt;span class="c"&gt;# Processes running in container&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp ps aux       &lt;span class="c"&gt;# More detailed process list&lt;/span&gt;

&lt;span class="c"&gt;# ===== STEP 6: Network Debugging =====&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].NetworkSettings.Networks'&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp netstat &lt;span class="nt"&gt;-tulpn&lt;/span&gt;    &lt;span class="c"&gt;# Listening ports&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp curl &lt;span class="nt"&gt;-v&lt;/span&gt; http://othercontainer:8080/health

&lt;span class="c"&gt;# Test DNS resolution in container&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp nslookup db        &lt;span class="c"&gt;# Can container resolve service name?&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp ping &lt;span class="nt"&gt;-c3&lt;/span&gt; db        &lt;span class="c"&gt;# Can container reach db?&lt;/span&gt;

&lt;span class="c"&gt;# ===== STEP 7: Docker Events =====&lt;/span&gt;
docker events &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s1"&gt;'container=myapp'&lt;/span&gt;
docker events &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s1"&gt;'container=myapp'&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt; 1h
&lt;span class="c"&gt;# Shows: die, kill, oom, start, stop, restart events&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Debugging Containers That Won't Start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container crashes immediately — override the entrypoint&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; /bin/sh myapp:latest
&lt;span class="c"&gt;# Or bash:&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; /bin/bash myapp:latest

&lt;span class="c"&gt;# Then manually run the CMD to see the exact error:&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;node server.js
&lt;span class="c"&gt;# Error: Cannot find module 'express'  ← npm install was never run!&lt;/span&gt;

&lt;span class="c"&gt;# Debug with a minimal command&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; myapp:latest &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"container works"&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; myapp:latest &lt;span class="nb"&gt;ls&lt;/span&gt; /app    &lt;span class="c"&gt;# Check if files are present&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; myapp:latest &lt;span class="nb"&gt;env&lt;/span&gt;        &lt;span class="c"&gt;# Check env vars&lt;/span&gt;

&lt;span class="c"&gt;# Check image layers/history for clues&lt;/span&gt;
docker &lt;span class="nb"&gt;history &lt;/span&gt;myapp:latest
docker inspect myapp:latest | jq &lt;span class="s1"&gt;'.[0].Config'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced Network Debugging — netshoot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# nicolaka/netshoot — network debugging toolkit&lt;/span&gt;
&lt;span class="c"&gt;# Contains: curl, wget, dig, nslookup, netstat, tcpdump, etc.&lt;/span&gt;

&lt;span class="c"&gt;# Share network namespace with target container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--net&lt;/span&gt; container:myapp &lt;span class="se"&gt;\&lt;/span&gt;
  nicolaka/netshoot

&lt;span class="c"&gt;# Now you have full network tools in myapp's network namespace&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;netstat &lt;span class="nt"&gt;-tulpn&lt;/span&gt;      &lt;span class="c"&gt;# See what myapp is listening on&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl localhost:3000 &lt;span class="c"&gt;# Test myapp's API&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;tcpdump &lt;span class="nt"&gt;-i&lt;/span&gt; eth0     &lt;span class="c"&gt;# Capture traffic&lt;/span&gt;

&lt;span class="c"&gt;# Share both network AND pid namespace&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--net&lt;/span&gt; container:myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pid&lt;/span&gt; container:myapp &lt;span class="se"&gt;\&lt;/span&gt;
  nicolaka/netshoot

&lt;span class="c"&gt;# Check for OOM kills in system logs&lt;/span&gt;
dmesg | &lt;span class="nb"&gt;grep &lt;/span&gt;oom
journalctl &lt;span class="nt"&gt;-k&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;oom | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Decoding exit codes — &lt;code&gt;Exited (0)&lt;/code&gt;: clean exit (intentional). &lt;code&gt;Exited (1)&lt;/code&gt;: application error. &lt;code&gt;Exited (137)&lt;/code&gt;: killed by OOM killer (memory limit exceeded). &lt;code&gt;Exited (139)&lt;/code&gt;: segfault. &lt;code&gt;Exited (143)&lt;/code&gt;: SIGTERM received (graceful stop). Exit codes reveal a lot before you even read the logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Alpine base images do not include bash, curl, or netstat by default. Install them temporarily for debugging: &lt;code&gt;docker exec myapp apk add --no-cache curl bash net-tools&lt;/code&gt;. For distroless images, use ephemeral debug containers (a Kubernetes feature).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Health Checks — Container Self-Assessment
&lt;/h2&gt;

&lt;p&gt;A running container does not mean the application is healthy. The process may be alive but unable to connect to the database, or not listening on the expected port. Health checks tell Docker whether the app is actually working.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Health check states — &lt;code&gt;starting&lt;/code&gt;: within the start_period, checks are ignored. &lt;code&gt;healthy&lt;/code&gt;: the most recent check passed. &lt;code&gt;unhealthy&lt;/code&gt;: retries exhausted, all checks failed. Docker Swarm and Kubernetes automatically restart unhealthy containers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Dockerfile Health Check Options
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# HTTP endpoint check (for REST APIs)&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s --start-period=30s --retries=3 \&lt;/span&gt;
  CMD curl -f http://localhost:3000/health || exit 1
&lt;span class="c"&gt;# --interval: How often to check (default: 30s)&lt;/span&gt;
&lt;span class="c"&gt;# --timeout: How long to wait for one check (default: 30s)&lt;/span&gt;
&lt;span class="c"&gt;# --start-period: Startup grace period — failed checks don't count (default: 0s)&lt;/span&gt;
&lt;span class="c"&gt;# --retries: How many times to retry before marking unhealthy (default: 3)&lt;/span&gt;
&lt;span class="c"&gt;# curl -f: Fails silently on HTTP errors (4xx, 5xx)&lt;/span&gt;

&lt;span class="c"&gt;# TCP port check (for databases or non-HTTP services)&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s \&lt;/span&gt;
  CMD nc -z localhost 5432 || exit 1
&lt;span class="c"&gt;# nc -z: Check if port is open (no data sent)&lt;/span&gt;

&lt;span class="c"&gt;# Node.js application check (when curl is not available)&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s --start-period=30s --retries=3 \&lt;/span&gt;
  CMD node -e "require('http').get('http://localhost:3000/health', (r) =&amp;gt; { process.exit(r.statusCode === 200 ? 0 : 1) })" || exit 1

&lt;span class="c"&gt;# Custom script (for complex checks)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; healthcheck.sh /usr/local/bin/healthcheck&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/healthcheck
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=10s --retries=3 \&lt;/span&gt;
  CMD /usr/local/bin/healthcheck

&lt;span class="c"&gt;# Disable inherited healthcheck (from base image)&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; NONE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Complex Health Check Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# healthcheck.sh — Multiple checks in one script&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Check 1: HTTP endpoint response&lt;/span&gt;
&lt;span class="nv"&gt;http_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; http://localhost:3000/health &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;--max-time&lt;/span&gt; 3&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$http_status&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UNHEALTHY: HTTP check failed with status &lt;/span&gt;&lt;span class="nv"&gt;$http_status&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check 2: Database connectivity&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; nc &lt;span class="nt"&gt;-z&lt;/span&gt; db 5432 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UNHEALTHY: Cannot reach database on port 5432"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check 3: Disk space (prevent crash due to full disk)&lt;/span&gt;
&lt;span class="nv"&gt;disk_usage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;df&lt;/span&gt; /app | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR==2 {print $5}'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/%//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$disk_usage&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 90 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UNHEALTHY: Disk usage too high: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;disk_usage&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"HEALTHY: All checks passed"&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Health Check in Docker Compose
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;   &lt;span class="c1"&gt;# PostgreSQL takes time to start!&lt;/span&gt;

  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapi:latest&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;40s&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;  &lt;span class="c1"&gt;# Wait for db to be HEALTHY, not just running!&lt;/span&gt;

  &lt;span class="na"&gt;worker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myworker:latest&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;  &lt;span class="c1"&gt;# Start worker only when api is healthy&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;

&lt;span class="c1"&gt;# depends_on with service_healthy = proper startup ordering!&lt;/span&gt;
&lt;span class="c1"&gt;# Without this: api starts before db is ready → connection error!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checking Health Status
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# docker ps shows health status&lt;/span&gt;
docker ps
&lt;span class="c"&gt;# CONTAINER ID  IMAGE    STATUS&lt;/span&gt;
&lt;span class="c"&gt;# abc123        myapp    Up 5 minutes (healthy)&lt;/span&gt;
&lt;span class="c"&gt;# def456        mydb     Up 2 minutes (starting)&lt;/span&gt;
&lt;span class="c"&gt;# ghi789        worker   Up 10 minutes (unhealthy)  ← Problem!&lt;/span&gt;

&lt;span class="c"&gt;# Get health status&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.Health.Status}}'&lt;/span&gt; myapp
&lt;span class="c"&gt;# healthy&lt;/span&gt;

&lt;span class="c"&gt;# Detailed health info with history&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .State.Health}}'&lt;/span&gt; myapp | jq
&lt;span class="c"&gt;# {&lt;/span&gt;
&lt;span class="c"&gt;#   "Status": "healthy",&lt;/span&gt;
&lt;span class="c"&gt;#   "FailingStreak": 0,&lt;/span&gt;
&lt;span class="c"&gt;#   "Log": [&lt;/span&gt;
&lt;span class="c"&gt;#     {&lt;/span&gt;
&lt;span class="c"&gt;#       "Start": "2024-01-15T...",&lt;/span&gt;
&lt;span class="c"&gt;#       "End": "2024-01-15T...",&lt;/span&gt;
&lt;span class="c"&gt;#       "ExitCode": 0,&lt;/span&gt;
&lt;span class="c"&gt;#       "Output": "HEALTHY: All checks passed"&lt;/span&gt;
&lt;span class="c"&gt;#     }&lt;/span&gt;
&lt;span class="c"&gt;#   ]&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;

&lt;span class="c"&gt;# Wait for container to be healthy (useful in scripts)&lt;/span&gt;
&lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.Health.Status}}'&lt;/span&gt; myapp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"healthy"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Waiting for myapp to be healthy..."&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;5
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"myapp is healthy!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Design a dedicated &lt;code&gt;/health&lt;/code&gt; endpoint that returns HTTP 200 when healthy and 503 when unhealthy. It should check database connectivity, required external services, and application state. Response time should be under 100ms — the health check itself should not be slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; If &lt;code&gt;start_period&lt;/code&gt; is too short and your app starts slowly, the container will immediately be marked "unhealthy" and enter a restart loop. Java apps may take 30–60 seconds. Node.js typically takes 5–10 seconds. Set the value based on your app's actual startup time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Docker's &lt;code&gt;HEALTHCHECK&lt;/code&gt; instruction does not work in Kubernetes — it is a Docker Compose feature. Kubernetes has its own &lt;code&gt;livenessProbe&lt;/code&gt;, &lt;code&gt;readinessProbe&lt;/code&gt;, and &lt;code&gt;startupProbe&lt;/code&gt;. The concept is the same, but the syntax differs. When migrating to Kubernetes, convert Dockerfile HEALTHCHECKs to Kubernetes probes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Development Workflows with Docker
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Docker for Local Development — The Real Game Changer
&lt;/h3&gt;

&lt;p&gt;Docker development environments ensure every developer — whether on Windows, Mac, or Linux — runs the exact same stack. New developer onboarding becomes: &lt;code&gt;git clone &amp;amp;&amp;amp; docker compose up&lt;/code&gt; — done in under 5 minutes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Docker dev environments eliminate "works on my machine" issues permanently. Every developer gets an identical stack, regardless of operating system.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.dev.yml - Development setup&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.dev&lt;/span&gt;  &lt;span class="c1"&gt;# Dev dockerfile with hot reload&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;                    &lt;span class="c1"&gt;# Mount source code!&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;         &lt;span class="c1"&gt;# Keep container's node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
      &lt;span class="na"&gt;DEBUG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9229:9229"&lt;/span&gt;               &lt;span class="c1"&gt;# Node.js debug port&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run dev&lt;/span&gt;          &lt;span class="c1"&gt;# Hot reload command&lt;/span&gt;

  &lt;span class="c1"&gt;# Development tools&lt;/span&gt;
  &lt;span class="na"&gt;mailhog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mailhog/mailhog&lt;/span&gt;        &lt;span class="c1"&gt;# Email testing&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8025:8025"&lt;/span&gt;               &lt;span class="c1"&gt;# Web UI&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1025:1025"&lt;/span&gt;               &lt;span class="c1"&gt;# SMTP&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dev-tools&lt;/span&gt;

  &lt;span class="na"&gt;redis-commander&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rediscommander/redis-commander&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_HOSTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;local:redis:6379"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8081:8081"&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dev-tools&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use profiles to start dev tools only when needed: &lt;code&gt;docker compose --profile dev-tools up&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile.dev — Node.js with Hot Reload
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile.dev - Development only&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;

&lt;span class="c"&gt;# Install nodemon for hot reload&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; nodemon

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy package files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;  &lt;span class="c"&gt;# All deps including devDependencies&lt;/span&gt;

&lt;span class="c"&gt;# Don't COPY source - it's mounted via volume!&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000 9229&lt;/span&gt;

&lt;span class="c"&gt;# Hot reload with nodemon&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nodemon", "--inspect=0.0.0.0:9229", "src/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Development vs Production — Key Differences
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Development&lt;/th&gt;
&lt;th&gt;Production&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base Image&lt;/td&gt;
&lt;td&gt;node:18 (full)&lt;/td&gt;
&lt;td&gt;node:18-alpine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;devDependencies&lt;/td&gt;
&lt;td&gt;Installed&lt;/td&gt;
&lt;td&gt;Excluded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source Code&lt;/td&gt;
&lt;td&gt;Via bind mount&lt;/td&gt;
&lt;td&gt;COPYed into image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot Reload&lt;/td&gt;
&lt;td&gt;nodemon&lt;/td&gt;
&lt;td&gt;Not needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug Ports&lt;/td&gt;
&lt;td&gt;9229 exposed&lt;/td&gt;
&lt;td&gt;No debug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image Size&lt;/td&gt;
&lt;td&gt;~1GB acceptable&lt;/td&gt;
&lt;td&gt;&amp;lt; 200MB target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-stage Build&lt;/td&gt;
&lt;td&gt;Not needed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Benefits of Docker for Development
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Same Environment&lt;/strong&gt;: Every developer runs an identical stack. "Works on my machine" becomes irrelevant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant Onboarding&lt;/strong&gt;: New developer joins: &lt;code&gt;git clone &amp;amp;&amp;amp; docker compose up&lt;/code&gt; = fully running stack in 5 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Version Conflicts&lt;/strong&gt;: Switch between Node 16, 18, 20 projects without juggling version managers. Each project is isolated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact Production Parity&lt;/strong&gt;: Test with the exact same database version (Postgres 16, MongoDB 7) as production.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Docker for React/Node Development
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This section covers CRA, Vite, Next.js, and full MERN stack setups with Docker, including real-world pitfalls that trip up beginners.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Dockerize Create React App (CRA) with Hot Reload
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile.dev for React CRA&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;# Don't copy source - mounted via volume&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "start"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml for React dev&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;react-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.dev&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;          &lt;span class="c1"&gt;# Prevent overwriting container's node_modules!&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CHOKIDAR_USEPOLLING=true&lt;/span&gt;   &lt;span class="c1"&gt;# Required for file watching in Docker!&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REACT_APP_API_URL=http://localhost:5000&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;               &lt;span class="c1"&gt;# Required for CRA&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Dockerize Vite React App
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Vite by default only listens on &lt;code&gt;localhost&lt;/code&gt; inside the container. The &lt;code&gt;--host 0.0.0.0&lt;/code&gt; flag is required to access it from outside the container.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile.dev for Vite&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5173&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "run", "dev", "--", "--host", "0.0.0.0"]&lt;/span&gt;
&lt;span class="c"&gt;# --host 0.0.0.0 is CRITICAL! Otherwise Vite only listens on localhost inside container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vite-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.dev&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5173:5173"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VITE_API_URL=http://localhost:3001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Production React App with Nginx
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile.prod - Complete production build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_ENV=production&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL=$REACT_APP_API_URL&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_ENV=$REACT_APP_ENV&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Production: Nginx serving&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.25-alpine&lt;/span&gt;

&lt;span class="c"&gt;# Custom nginx config for React SPA&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;

&lt;span class="c"&gt;# Copy built React app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/build /usr/share/nginx/html&lt;/span&gt;

&lt;span class="c"&gt;# Health endpoint&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"healthy"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /usr/share/nginx/html/health

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=3s \&lt;/span&gt;
  CMD wget -qO- http://localhost/health || exit 1

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/usr/share/nginx/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# React Router support — REQUIRED for SPAs!&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# API proxy (optional)&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/api/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://api:5000/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Cache static assets aggressively&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)&lt;/span&gt;$ &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;expires&lt;/span&gt; &lt;span class="s"&gt;1y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"public,&lt;/span&gt; &lt;span class="s"&gt;immutable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Gzip compression&lt;/span&gt;
    &lt;span class="kn"&gt;gzip&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;gzip_vary&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;gzip_types&lt;/span&gt; &lt;span class="nc"&gt;text/plain&lt;/span&gt; &lt;span class="nc"&gt;text/css&lt;/span&gt; &lt;span class="nc"&gt;application/json&lt;/span&gt; &lt;span class="nc"&gt;application/javascript&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Next.js Production — Official Recommended Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; libc6-compat
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_TELEMETRY_DISABLED=1&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runner&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NEXT_TELEMETRY_DISABLED=1&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; 1001 nodejs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--uid&lt;/span&gt; 1001 nextjs
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/public ./public&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/standalone ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nextjs&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Full MERN Stack Development Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml - Full MERN Dev Stack&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# React Frontend&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.dev&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./frontend:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REACT_APP_API_URL=http://localhost:5000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CHOKIDAR_USEPOLLING=true&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;

  &lt;span class="c1"&gt;# Node.js API&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./api&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.dev&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./api:/app&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:5000"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9229:9229"&lt;/span&gt;   &lt;span class="c1"&gt;# Debug port&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
      &lt;span class="na"&gt;MONGODB_URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb://mongodb:27017/myapp&lt;/span&gt;
      &lt;span class="na"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev-secret-key&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;

  &lt;span class="c1"&gt;# MongoDB&lt;/span&gt;
  &lt;span class="na"&gt;mongodb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo:7&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;27017:27017"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo_data:/data/db&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MONGO_INITDB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;

  &lt;span class="c1"&gt;# Redis cache&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379:6379"&lt;/span&gt;

  &lt;span class="c1"&gt;# Mongo Express - GUI for MongoDB (localhost:8081)&lt;/span&gt;
  &lt;span class="na"&gt;mongo-express&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo-express&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8081:8081"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ME_CONFIG_MONGODB_SERVER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;
      &lt;span class="na"&gt;ME_CONFIG_BASICAUTH_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
      &lt;span class="na"&gt;ME_CONFIG_BASICAUTH_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mongo_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common React + Docker Problems and Fixes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hot reload not working&lt;/td&gt;
&lt;td&gt;File watching broken in Docker&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;CHOKIDAR_USEPOLLING=true&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can't access Vite app&lt;/td&gt;
&lt;td&gt;Vite binding to localhost only&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;--host 0.0.0.0&lt;/code&gt; to dev command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;node_modules overwritten&lt;/td&gt;
&lt;td&gt;Host volume masks container deps&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;/app/node_modules&lt;/code&gt; as anonymous volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build fails: env vars missing&lt;/td&gt;
&lt;td&gt;REACT_APP_ prefix not passed at build time&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;ARG&lt;/code&gt; + &lt;code&gt;ENV&lt;/code&gt; in Dockerfile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Very slow Docker builds&lt;/td&gt;
&lt;td&gt;Large build context with node_modules&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;node_modules&lt;/code&gt; to &lt;code&gt;.dockerignore&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Debugging Dockerized Apps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Node.js Remote Debugging
&lt;/h3&gt;

&lt;p&gt;You can debug a Node.js app running inside a container using Chrome DevTools or VS Code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start Node.js container in debug mode&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; api-debug &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 9229:9229 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app &lt;span class="se"&gt;\&lt;/span&gt;
  node:18 &lt;span class="se"&gt;\&lt;/span&gt;
  node &lt;span class="nt"&gt;--inspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0:9229 src/server.js

&lt;span class="c"&gt;# Then open Chrome: chrome://inspect&lt;/span&gt;
&lt;span class="c"&gt;# Your server will appear under "Remote Target"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;VS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;launch.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Docker: Attach to Node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"localRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"remoteRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"restart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python Debugging with debugpy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Python debugging with debugpy&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5678:5678 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/app &lt;span class="se"&gt;\&lt;/span&gt;
  python:3.11 &lt;span class="se"&gt;\&lt;/span&gt;
  python &lt;span class="nt"&gt;-m&lt;/span&gt; debugpy &lt;span class="nt"&gt;--listen&lt;/span&gt; 0.0.0.0:5678 &lt;span class="nt"&gt;--wait-for-client&lt;/span&gt; app.py

&lt;span class="c"&gt;# In VS Code, use the Python: Remote Attach configuration&lt;/span&gt;
&lt;span class="c"&gt;# Port: 5678, Host: localhost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Network Debugging with netshoot
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;nicolaka/netshoot&lt;/code&gt; contains curl, tcpdump, netstat, nslookup, traceroute, dig — everything you need. Attach to any container's network namespace and debug immediately.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Network debugging with netshoot&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; container:myapp &lt;span class="se"&gt;\&lt;/span&gt;
  nicolaka/netshoot

&lt;span class="c"&gt;# Now inside myapp's network namespace with full tooling:&lt;/span&gt;
&lt;span class="c"&gt;# curl, tcpdump, netstat, nslookup, traceroute, dig, etc.&lt;/span&gt;

&lt;span class="c"&gt;# Check DNS resolution inside container network&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; mynetwork nicolaka/netshoot nslookup api

&lt;span class="c"&gt;# Check if port is reachable between services&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; mynetwork nicolaka/netshoot nc &lt;span class="nt"&gt;-zv&lt;/span&gt; api 5000

&lt;span class="c"&gt;# Capture TCP traffic&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; container:myapp nicolaka/netshoot tcpdump &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 port 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  General Container Debugging Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Open a shell inside the container&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp sh

&lt;span class="c"&gt;# View logs with timestamps&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;--timestamps&lt;/span&gt; myapp

&lt;span class="c"&gt;# Process list inside container&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp ps aux

&lt;span class="c"&gt;# Check environment variables&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp &lt;span class="nb"&gt;env&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; api

&lt;span class="c"&gt;# Full container config&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].NetworkSettings'&lt;/span&gt;

&lt;span class="c"&gt;# Browse the filesystem&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp find /app &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.log"&lt;/span&gt;

&lt;span class="c"&gt;# Copy files out of container for inspection&lt;/span&gt;
docker &lt;span class="nb"&gt;cp &lt;/span&gt;myapp:/app/logs/error.log ./error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Production Readiness Checklist
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Before going to production, verify all items below. Common mistakes in production Dockerfiles and compose files create security vulnerabilities, crashes, or performance issues. Bookmark this checklist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Production Dockerfile Checklist
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Specific base image version (not &lt;code&gt;latest&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Reproducible builds, no surprise updates&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-stage build used&lt;/td&gt;
&lt;td&gt;Smaller final image, no build tools in prod&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-root user configured&lt;/td&gt;
&lt;td&gt;Security — limits blast radius of a breach&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.dockerignore present&lt;/td&gt;
&lt;td&gt;Smaller build context, no secrets leaked&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEALTHCHECK configured&lt;/td&gt;
&lt;td&gt;Orchestrators can detect unhealthy containers&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WORKDIR explicitly set&lt;/td&gt;
&lt;td&gt;Predictable file paths&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;package*.json COPY before source COPY&lt;/td&gt;
&lt;td&gt;Better layer caching&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No secrets in image/Dockerfile&lt;/td&gt;
&lt;td&gt;Security — images can be inspected&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image size &amp;lt; 500MB (ideally &amp;lt; 200MB)&lt;/td&gt;
&lt;td&gt;Faster pulls, lower storage cost&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LABEL with version/maintainer info&lt;/td&gt;
&lt;td&gt;Traceability in production&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graceful shutdown handled (SIGTERM)&lt;/td&gt;
&lt;td&gt;Zero-downtime deployments&lt;/td&gt;
&lt;td&gt;☐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Graceful Shutdown in Node.js
&lt;/h3&gt;

&lt;p&gt;When Docker stops a container, it sends &lt;code&gt;SIGTERM&lt;/code&gt; first, then &lt;code&gt;SIGKILL&lt;/code&gt; after 10 seconds. If you do not handle SIGTERM, in-flight requests are dropped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js - Handle SIGTERM for graceful shutdown&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM received, shutting down gracefully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HTTP server closed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Close DB connections&lt;/span&gt;
    &lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MongoDB disconnected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Force exit after 30 seconds&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not close connections in time, forcing exit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Image Versioning Strategy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Tagging strategy for production&lt;/span&gt;
&lt;span class="nv"&gt;IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"myapp"&lt;/span&gt;
&lt;span class="nv"&gt;REGISTRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"123456789.dkr.ecr.ap-south-1.amazonaws.com"&lt;/span&gt;
&lt;span class="nv"&gt;GIT_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git describe &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nt"&gt;--always&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;GIT_SHA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Always push with multiple tags&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$IMAGE&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$IMAGE&lt;/span&gt; &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE&lt;/span&gt;:&lt;span class="nv"&gt;$GIT_TAG&lt;/span&gt;       &lt;span class="c"&gt;# v1.2.3&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$IMAGE&lt;/span&gt; &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE&lt;/span&gt;:&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt;       &lt;span class="c"&gt;# abc1234&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$IMAGE&lt;/span&gt; &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE&lt;/span&gt;:&lt;span class="nv"&gt;$DATE&lt;/span&gt;          &lt;span class="c"&gt;# 20240115&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$IMAGE&lt;/span&gt; &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE&lt;/span&gt;:latest         &lt;span class="c"&gt;# latest (don't use in deploys!)&lt;/span&gt;

&lt;span class="c"&gt;# Deploy using a specific version, not latest!&lt;/span&gt;
docker pull &lt;span class="nv"&gt;$REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$IMAGE&lt;/span&gt;:v1.2.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Never deploy using the &lt;code&gt;:latest&lt;/code&gt; tag in production. If someone pushes and the service restarts, an unexpected version may be deployed. Always use a git SHA or semantic version tag for deployments.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker with CI/CD Pipelines
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complete GitHub Actions Workflow — Build, Test, Push
&lt;/h3&gt;

&lt;p&gt;Before writing your pipeline, validate your workflow syntax with the &lt;a href="https://opscanopy.com/github-actions-validator/" rel="noopener noreferrer"&gt;GitHub Actions Validator&lt;/a&gt; to catch errors early.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/docker-build-push.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Push Docker Image&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v*'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;REGISTRY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
  &lt;span class="na"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.repository }}&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;docker build --target test -t myapp:test .&lt;/span&gt;
          &lt;span class="s"&gt;docker run --rm myapp:test npm test&lt;/span&gt;

  &lt;span class="na"&gt;build-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name != 'pull_request'&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to GHCR&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.REGISTRY }}&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract metadata (tags, labels)&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/metadata-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;type=semver,pattern={{version}}&lt;/span&gt;
            &lt;span class="s"&gt;type=semver,pattern={{major}}.{{minor}}&lt;/span&gt;
            &lt;span class="s"&gt;type=sha,prefix=sha-&lt;/span&gt;
            &lt;span class="s"&gt;type=raw,value=latest,enable={{is_default_branch}}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.tags }}&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.meta.outputs.labels }}&lt;/span&gt;
          &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;          &lt;span class="c1"&gt;# Use GitHub Actions cache!&lt;/span&gt;
          &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64&lt;/span&gt;   &lt;span class="c1"&gt;# Multi-arch build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy to AWS ECS via CI/CD
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-push&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-south-1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download task definition&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws ecs describe-task-definition \&lt;/span&gt;
            &lt;span class="s"&gt;--task-definition myapp \&lt;/span&gt;
            &lt;span class="s"&gt;--query taskDefinition &amp;gt; task-definition.json&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update ECS task definition&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;task-def&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecs-render-task-definition@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;task-definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;task-definition.json&lt;/span&gt;
          &lt;span class="na"&gt;container-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to ECS&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecs-deploy-task-definition@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;task-definition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.task-def.outputs.task-definition }}&lt;/span&gt;
          &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp-service&lt;/span&gt;
          &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
          &lt;span class="na"&gt;wait-for-service-stability&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;   &lt;span class="c1"&gt;# Wait for deploy to complete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using GitLab CI instead, validate your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; syntax with the &lt;a href="https://opscanopy.com/gitlab-ci-validator/" rel="noopener noreferrer"&gt;GitLab CI Validator&lt;/a&gt; before pushing.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD Pipeline Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Code Push&lt;/strong&gt;: Developer runs &lt;code&gt;git push&lt;/code&gt; → GitHub Actions triggers automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test&lt;/strong&gt;: Docker image builds with test target → tests run inside the container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build and Push&lt;/strong&gt;: Multi-arch production image builds and pushes to registry with proper tags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt;: ECS task definition updates → service performs a rolling deploy with zero downtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; "Our CI/CD pipeline runs on GitHub Actions. Every push to main automatically builds the Docker image, runs tests inside the container, then pushes to ECR and does an ECS rolling deploy — all automated. Manual production deployments never happen."&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;h3&gt;
  
  
  Docker Built-in Monitoring Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Real-time stats — CPU, Memory, Network, IO&lt;/span&gt;
docker stats                    &lt;span class="c"&gt;# All containers&lt;/span&gt;
docker stats myapp &lt;span class="nt"&gt;--no-stream&lt;/span&gt;  &lt;span class="c"&gt;# One-time snapshot (no live update)&lt;/span&gt;

&lt;span class="c"&gt;# Human-readable format&lt;/span&gt;
docker stats &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.Container}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.CPUPerc}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.MemUsage}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.NetIO}}"&lt;/span&gt;

&lt;span class="c"&gt;# Events stream — what is happening in the system&lt;/span&gt;
docker events &lt;span class="nt"&gt;--since&lt;/span&gt; 1h

&lt;span class="c"&gt;# Filter events by type&lt;/span&gt;
docker events &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;container &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;die

&lt;span class="c"&gt;# Logs with time range&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; 1h &lt;span class="nt"&gt;--until&lt;/span&gt; 30m myapp
docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"2024-01-15T10:00:00"&lt;/span&gt; myapp

&lt;span class="c"&gt;# Formatted logs&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--timestamps&lt;/span&gt; myapp 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep &lt;/span&gt;ERROR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Full Monitoring Stack: cAdvisor + Prometheus + Grafana
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# monitoring/docker-compose.monitoring.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus_data:/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--config.file=/etc/prometheus/prometheus.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--storage.tsdb.retention.time=30d&lt;/span&gt;

  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3000"&lt;/span&gt;             &lt;span class="c1"&gt;# Port 3000 reserved for app&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GF_SECURITY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GRAFANA_PASSWORD:-admin123}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;grafana_data:/var/lib/grafana&lt;/span&gt;

  &lt;span class="na"&gt;cadvisor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/cadvisor/cadvisor:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/:/rootfs:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run:/var/run:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/sys:/sys:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker/:/var/lib/docker:ro&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# prometheus.yml&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15s&lt;/span&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docker'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cadvisor:8080'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;    &lt;span class="c1"&gt;# Container metrics&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;myapp'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;myapp:3000'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;metrics_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/metrics'&lt;/span&gt;          &lt;span class="c1"&gt;# App must expose a /metrics endpoint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What to Monitor
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU Usage&lt;/strong&gt;: Detect container CPU throttling. Consistently above 80% means scale out or raise resource limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: Detect memory leaks. Check for OOM kills with &lt;code&gt;docker events --filter event=oom&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network I/O&lt;/strong&gt;: Detect unusual traffic patterns. Track high egress costs in AWS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container Restarts&lt;/strong&gt;: Frequent restarts indicate a crash loop. Check the RESTARTS column in &lt;code&gt;docker ps&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Docker Orchestration Introduction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Docker Swarm vs Kubernetes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Docker Swarm&lt;/th&gt;
&lt;th&gt;Kubernetes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;Simple&lt;/td&gt;
&lt;td&gt;Complex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup Time&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Hours/Days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-scaling&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Advanced (HPA, VPA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem&lt;/td&gt;
&lt;td&gt;Docker only&lt;/td&gt;
&lt;td&gt;Massive (CNCF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rolling Updates&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-healing&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-host&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Industry Adoption (2026)&lt;/td&gt;
&lt;td&gt;Declining/Legacy&lt;/td&gt;
&lt;td&gt;Dominant Standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Native Support&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;First-class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning Curve&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Swarm is now mostly legacy. In 2026, Kubernetes is the industry standard. That said, mastering Docker Compose and core Docker concepts makes learning Kubernetes significantly easier — the concepts are the same, just with greater complexity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  When Do You Need Orchestration?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scale: 10+ Containers&lt;/strong&gt;: Docker Compose runs on a single machine. Deploying across multiple machines requires orchestration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Availability&lt;/strong&gt;: If a node dies and the app automatically shifts to another node — that is orchestration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scaling&lt;/strong&gt;: Automatically scale containers up when traffic increases and scale down when it drops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-downtime Deploys&lt;/strong&gt;: Rolling deployments where the old version keeps running until the new version is healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Transition Path
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Learning path: Docker basics → Docker Compose (multi-container) → Kubernetes (multi-host, production-grade). Each step builds on the previous one. Master Docker concepts and Kubernetes will feel natural. When you are ready to take the next step, the &lt;a href="https://opscanopy.com/learn/guides/kubernetes-for-devops/" rel="noopener noreferrer"&gt;Kubernetes for DevOps&lt;/a&gt; guide picks up exactly where containers leave off.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Docker Swarm quick demo (educational only)&lt;/span&gt;
docker swarm init                       &lt;span class="c"&gt;# Initialize swarm on current node&lt;/span&gt;
docker service create &lt;span class="nt"&gt;--name&lt;/span&gt; web nginx  &lt;span class="c"&gt;# Create a service&lt;/span&gt;
docker service scale &lt;span class="nv"&gt;web&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5              &lt;span class="c"&gt;# Scale to 5 replicas (auto-distributed!)&lt;/span&gt;
docker service &lt;span class="nb"&gt;ls&lt;/span&gt;                       &lt;span class="c"&gt;# List services&lt;/span&gt;
docker service ps web                   &lt;span class="c"&gt;# See where each replica runs&lt;/span&gt;
docker swarm leave &lt;span class="nt"&gt;--force&lt;/span&gt;              &lt;span class="c"&gt;# Leave swarm&lt;/span&gt;

&lt;span class="c"&gt;# Equivalent nginx deployment in Kubernetes&lt;/span&gt;
kubectl create deployment web &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx
kubectl scale deployment web &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
kubectl get deployments
kubectl get pods                        &lt;span class="c"&gt;# See 5 pods running&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  AWS Container Services
&lt;/h2&gt;

&lt;h3&gt;
  
  
  AWS Container Services Overview
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;What It Is&lt;/th&gt;
&lt;th&gt;When to Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ECR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Elastic Container Registry — private image registry&lt;/td&gt;
&lt;td&gt;Store Docker images for AWS deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ECS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Managed container orchestration service&lt;/td&gt;
&lt;td&gt;Production workloads, teams familiar with Docker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fargate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Serverless containers — no EC2 management&lt;/td&gt;
&lt;td&gt;Simple deployments, variable load, no infrastructure operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EKS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Managed Kubernetes service&lt;/td&gt;
&lt;td&gt;Complex microservices, existing Kubernetes expertise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;App Runner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully managed container platform&lt;/td&gt;
&lt;td&gt;Simple web apps, fastest time-to-deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  ECS Architecture
&lt;/h3&gt;

&lt;p&gt;Understanding how ECS maps to Docker concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task Definition&lt;/strong&gt; — Blueprint for container configuration, similar to a &lt;code&gt;docker-compose.yml&lt;/code&gt;. Defines image, ports, environment variables, CPU and memory limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task&lt;/strong&gt; — A running instance of a task definition. Equivalent to a running container or group of containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service&lt;/strong&gt; — Maintains the desired task count, automatically replaces failed tasks, and integrates with a load balancer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster&lt;/strong&gt; — A group of EC2 instances or Fargate compute resources on which tasks are scheduled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ECS Task Definition — Complete Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"family"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"myapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"networkMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiresCompatibilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cpu"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"512"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executionRoleArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789:role/ecsTaskExecutionRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"taskRoleArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789:role/ecsTaskRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"containerDefinitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"myapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789.dkr.ecr.ap-south-1.amazonaws.com/myapp:v1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"portMappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"containerPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tcp"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_ENV"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"secrets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"valueFrom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:ap-south-1:123456789:secret:myapp/db-url"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"logConfiguration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"logDriver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"awslogs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"awslogs-group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/ecs/myapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"awslogs-region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ap-south-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"awslogs-stream-prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecs"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"healthCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"CMD-SHELL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"curl -f http://localhost:3000/health || exit 1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"interval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"retries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"startPeriod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Never store secrets directly in environment variable definitions. Use AWS Secrets Manager or Parameter Store. ECS automatically injects secrets as environment variables at runtime — they are not stored in the image.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Practical AWS Deployment Workflows
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complete Deployment Script — React App to AWS Fargate
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Copy this script into your project, set the variables to match your setup, and deploy your entire application with a single command: &lt;code&gt;bash deploy.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# deploy.sh - Complete deployment script&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;   &lt;span class="c"&gt;# Stop the script if any step fails&lt;/span&gt;

&lt;span class="c"&gt;# Variables — set your own values here&lt;/span&gt;
&lt;span class="nv"&gt;APP_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"myreactapp"&lt;/span&gt;
&lt;span class="nv"&gt;AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"123456789012"&lt;/span&gt;
&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ap-south-1"&lt;/span&gt;
&lt;span class="nv"&gt;ECR_REGISTRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="s2"&gt;.dkr.ecr.&lt;/span&gt;&lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com"&lt;/span&gt;
&lt;span class="nv"&gt;IMAGE_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--short&lt;/span&gt; HEAD&lt;span class="si"&gt;)&lt;/span&gt;   &lt;span class="c"&gt;# Use Git SHA as tag&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting deployment of &lt;/span&gt;&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$IMAGE_TAG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Step 1: Build image&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Building Docker image..."&lt;/span&gt;
docker build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;REACT_APP_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.myapp.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$IMAGE_TAG&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Authenticate with ECR&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Authenticating with ECR..."&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="nv"&gt;$ECR_REGISTRY&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: Tag for ECR&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$IMAGE_TAG&lt;/span&gt; &lt;span class="nv"&gt;$ECR_REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$IMAGE_TAG&lt;/span&gt;
docker tag &lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$IMAGE_TAG&lt;/span&gt; &lt;span class="nv"&gt;$ECR_REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;:latest

&lt;span class="c"&gt;# Step 4: Push to ECR&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Pushing to ECR..."&lt;/span&gt;
docker push &lt;span class="nv"&gt;$ECR_REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;:&lt;span class="nv"&gt;$IMAGE_TAG&lt;/span&gt;
docker push &lt;span class="nv"&gt;$ECR_REGISTRY&lt;/span&gt;/&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;:latest

&lt;span class="c"&gt;# Step 5: Update ECS service (force new deployment)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Updating ECS service..."&lt;/span&gt;
aws ecs update-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt; &lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="nt"&gt;-service&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--force-new-deployment&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;

&lt;span class="c"&gt;# Step 6: Wait for deployment to stabilize&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Waiting for deployment to stabilize..."&lt;/span&gt;
aws ecs &lt;span class="nb"&gt;wait &lt;/span&gt;services-stable &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--services&lt;/span&gt; &lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="nt"&gt;-service&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deployment successful! Image: &lt;/span&gt;&lt;span class="nv"&gt;$ECR_REGISTRY&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$APP_NAME&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$IMAGE_TAG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AWS Fargate with Application Load Balancer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create ECS cluster (Fargate mode)&lt;/span&gt;
aws ecs create-cluster &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; production

&lt;span class="c"&gt;# Create ALB target group&lt;/span&gt;
aws elbv2 create-target-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; myapp-tg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--protocol&lt;/span&gt; HTTP &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-12345 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-type&lt;/span&gt; ip &lt;span class="se"&gt;\ &lt;/span&gt;             &lt;span class="c"&gt;# Use 'ip' for Fargate, not 'instance'&lt;/span&gt;
  &lt;span class="nt"&gt;--health-check-path&lt;/span&gt; /health

&lt;span class="c"&gt;# Create ECS service with Fargate — 2 replicas for high availability&lt;/span&gt;
aws ecs create-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-name&lt;/span&gt; myapp-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--task-definition&lt;/span&gt; myapp:1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--desired-count&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--launch-type&lt;/span&gt; FARGATE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network-configuration&lt;/span&gt; &lt;span class="s2"&gt;"awsvpcConfiguration={subnets=[subnet-1,subnet-2],securityGroups=[sg-123],assignPublicIp=ENABLED}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--load-balancers&lt;/span&gt; &lt;span class="s2"&gt;"targetGroupArn=arn:aws:elasticloadbalancing:...,containerName=myapp,containerPort=3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ECR — Common Operations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create ECR repository&lt;/span&gt;
aws ecr create-repository &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-scanning-configuration&lt;/span&gt; &lt;span class="nv"&gt;scanOnPush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;  &lt;span class="c"&gt;# Enable vulnerability scanning&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1

&lt;span class="c"&gt;# List images in repository&lt;/span&gt;
aws ecr list-images &lt;span class="nt"&gt;--repository-name&lt;/span&gt; myapp &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1

&lt;span class="c"&gt;# Delete old images (cost management)&lt;/span&gt;
aws ecr batch-delete-image &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-ids&lt;/span&gt; &lt;span class="nv"&gt;imageTag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;old-tag

&lt;span class="c"&gt;# Lifecycle policy — automatically delete old images&lt;/span&gt;
aws ecr put-lifecycle-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--lifecycle-policy-text&lt;/span&gt; &lt;span class="s1"&gt;'{
    "rules": [{
      "rulePriority": 1,
      "description": "Keep last 10 images",
      "selection": {
        "tagStatus": "any",
        "countType": "imageCountMoreThan",
        "countNumber": 10
      },
      "action": {"type": "expire"}
    }]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10 Hands-On Docker Projects
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Build all of these projects and push them to GitHub. Each project can become a resume bullet point. Recruiters and interviewers want to see working code — that is far more impactful than simply claiming Docker knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Project 1: React Portfolio Site
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Beginner | &lt;strong&gt;Stack:&lt;/strong&gt; React, Nginx, Docker | &lt;strong&gt;Concepts:&lt;/strong&gt; Multi-stage build, static file serving&lt;/p&gt;

&lt;p&gt;Convert a React application into a production-ready Docker image using Nginx for serving and optimal caching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: Build React app&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Stage 2: Serve with Nginx&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:1.25-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/build /usr/share/nginx/html&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx.conf /etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["nginx", "-g", "daemon off;"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; portfolio &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; portfolio portfolio
&lt;span class="c"&gt;# Visit: http://localhost:8080&lt;/span&gt;

&lt;span class="c"&gt;# Check image size&lt;/span&gt;
docker images portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Resume bullet — &lt;em&gt;"Containerized React portfolio using Docker multi-stage builds with Nginx, achieving 90%+ image size reduction vs non-optimized builds; deployed via CI/CD pipeline."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Project 2: Secure Node.js REST API
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Beginner | &lt;strong&gt;Stack:&lt;/strong&gt; Node.js, Express, Docker | &lt;strong&gt;Concepts:&lt;/strong&gt; Non-root user, health checks, production dependencies only&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;

&lt;span class="c"&gt;# Security: create a non-root user&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;-S&lt;/span&gt; appuser &lt;span class="nt"&gt;-G&lt;/span&gt; appgroup

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production    &lt;span class="c"&gt;# Only production dependencies&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appgroup . .&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser                    # Switch to non-root user&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s --start-period=10s \&lt;/span&gt;
  CMD node -e "require('http').get('http://localhost:3000/health', r =&amp;gt; process.exit(r.statusCode === 200 ? 0 : 1))"

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; node-api &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 node-api

&lt;span class="c"&gt;# Check health check status&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{{.State.Health.Status}}'&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Test the API&lt;/span&gt;
curl http://localhost:3000/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Project 3: Python FastAPI Data App
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Beginner | &lt;strong&gt;Stack:&lt;/strong&gt; Python, FastAPI, Uvicorn | &lt;strong&gt;Concepts:&lt;/strong&gt; Python containerization, pip caching&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim           # slim = smaller than full python image&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Copy requirements first for better layer caching&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PYTHONUNBUFFERED=1          # Python logs appear in Docker logs&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; fastapi-app &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 fastapi-app

&lt;span class="c"&gt;# Interactive API docs&lt;/span&gt;
&lt;span class="c"&gt;# http://localhost:8000/docs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Project 4: Full MERN Stack Application
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate | &lt;strong&gt;Stack:&lt;/strong&gt; React, Node.js, MongoDB, Redis | &lt;strong&gt;Concepts:&lt;/strong&gt; Multi-service Compose, service discovery, volumes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml - Full MERN Stack&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REACT_APP_API_URL=http://localhost:5000&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:80"&lt;/span&gt;               &lt;span class="c1"&gt;# Production: nginx serving&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;

  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:5000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MONGODB_URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb://mongodb:27017/mern_db&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://redis:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

  &lt;span class="na"&gt;mongodb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo:7&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongo_data:/data/db&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;

  &lt;span class="na"&gt;mongo-express&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;              &lt;span class="c1"&gt;# MongoDB GUI — development only&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongo-express&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8081:8081"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ME_CONFIG_MONGODB_SERVER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mongo_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start the complete MERN stack&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;

&lt;span class="c"&gt;# Check all services&lt;/span&gt;
docker compose ps

&lt;span class="c"&gt;# Follow API logs&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; backend

&lt;span class="c"&gt;# Access points:&lt;/span&gt;
&lt;span class="c"&gt;# Frontend: http://localhost:3000&lt;/span&gt;
&lt;span class="c"&gt;# Backend API: http://localhost:5000&lt;/span&gt;
&lt;span class="c"&gt;# MongoDB GUI: http://localhost:8081&lt;/span&gt;

&lt;span class="c"&gt;# Stop the stack (data is preserved)&lt;/span&gt;
docker compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Resume bullet — &lt;em&gt;"Containerized full MERN stack using Docker Compose with service discovery, named volumes for data persistence, and Mongo Express GUI for development; reduced environment setup time from 2 hours to 5 minutes."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Project 5: WordPress + MySQL — Classic Stack
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate | &lt;strong&gt;Stack:&lt;/strong&gt; WordPress, MySQL 8 | &lt;strong&gt;Concepts:&lt;/strong&gt; Health checks with conditions, restart policies, named volumes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mysql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:8.0&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rootpassword&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpresspassword&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mysql_data:/var/lib/mysql&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mysqladmin"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-h"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;

  &lt;span class="na"&gt;wordpress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:80"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;WORDPRESS_DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:3306&lt;/span&gt;
      &lt;span class="na"&gt;WORDPRESS_DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
      &lt;span class="na"&gt;WORDPRESS_DB_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpresspassword&lt;/span&gt;
      &lt;span class="na"&gt;WORDPRESS_DB_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wordpress&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wordpress_data:/var/www/html&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mysql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;    &lt;span class="c1"&gt;# Wait until MySQL is healthy&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mysql_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;wordpress_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="c"&gt;# MySQL health check will be satisfied before WordPress starts&lt;/span&gt;
docker compose ps

&lt;span class="c"&gt;# Visit: http://localhost:8080&lt;/span&gt;
&lt;span class="c"&gt;# Complete the WordPress setup wizard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Project 6: Microservices with Nginx API Gateway
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate | &lt;strong&gt;Stack:&lt;/strong&gt; Node.js microservices, Nginx gateway, PostgreSQL | &lt;strong&gt;Concepts:&lt;/strong&gt; API gateway pattern, service isolation, multiple databases&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Single entry point — all traffic routes through here&lt;/span&gt;
  &lt;span class="na"&gt;api-gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/gateway.conf:/etc/nginx/conf.d/default.conf&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;user-service&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;product-service&lt;/span&gt;

  &lt;span class="na"&gt;user-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./user-service&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DB_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://postgres:password@user-db:5432/users&lt;/span&gt;
    &lt;span class="c1"&gt;# No ports exposed externally — only accessible through the gateway&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;user-db&lt;/span&gt;

  &lt;span class="na"&gt;product-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./product-service&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DB_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://postgres:password@product-db:5432/products&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;product-db&lt;/span&gt;

  &lt;span class="c1"&gt;# Each service has its own database&lt;/span&gt;
  &lt;span class="na"&gt;user-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;user_db_data:/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;product-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;products&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;product_db_data:/var/lib/postgresql/data&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;user_db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;product_db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Resume bullet — &lt;em&gt;"Designed and containerized microservices architecture with Nginx API gateway, service isolation, and per-service databases using Docker Compose; demonstrating event-driven service communication patterns."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Project 7: Image Size Optimization Challenge
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Intermediate | &lt;strong&gt;Goal:&lt;/strong&gt; 1.2 GB → under 100 MB | &lt;strong&gt;Concepts:&lt;/strong&gt; Multi-stage builds, Alpine, production deps, .dockerignore&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# BEFORE: Naive approach — DO NOT USE in production&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs npm
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;              &lt;span class="c"&gt;# All deps including devDependencies&lt;/span&gt;
&lt;span class="c"&gt;# Final image size: ~1.2 GB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# AFTER: Optimized multi-stage build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci                   &lt;span class="c"&gt;# All deps for building&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build            &lt;span class="c"&gt;# Compile/transpile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine          # Fresh slim base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="c"&gt;# Production deps only&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node                    # Non-root&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;span class="c"&gt;# Final image size: ~85 MB (93% reduction)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Compare before and after sizes&lt;/span&gt;
docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.before &lt;span class="nt"&gt;-t&lt;/span&gt; app:before &lt;span class="nb"&gt;.&lt;/span&gt;
docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.after &lt;span class="nt"&gt;-t&lt;/span&gt; app:after &lt;span class="nb"&gt;.&lt;/span&gt;
docker images | &lt;span class="nb"&gt;grep &lt;/span&gt;app

&lt;span class="c"&gt;# Detailed layer analysis&lt;/span&gt;
docker &lt;span class="nb"&gt;history &lt;/span&gt;app:after

&lt;span class="c"&gt;# Layer-by-layer analysis with dive&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
  wagoodman/dive app:after
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Project 8: Full Monitoring Stack
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Advanced | &lt;strong&gt;Stack:&lt;/strong&gt; App + Prometheus + Grafana + cAdvisor | &lt;strong&gt;Concepts:&lt;/strong&gt; Metrics collection, dashboards, alerting&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="c1"&gt;# App must expose a /metrics endpoint (use prom-client npm package)&lt;/span&gt;

  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus_data:/prometheus&lt;/span&gt;

  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3000"&lt;/span&gt;            &lt;span class="c1"&gt;# Use 3001 to avoid conflict with the app&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;grafana_data:/var/lib/grafana&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;

  &lt;span class="na"&gt;cadvisor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/cadvisor/cadvisor&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/:/rootfs:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run:/var/run:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/sys:/sys:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker:/var/lib/docker:ro&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





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

&lt;span class="c"&gt;# Access points:&lt;/span&gt;
&lt;span class="c"&gt;# App: http://localhost:3000&lt;/span&gt;
&lt;span class="c"&gt;# Prometheus: http://localhost:9090&lt;/span&gt;
&lt;span class="c"&gt;# Grafana: http://localhost:3001 (admin/admin)&lt;/span&gt;
&lt;span class="c"&gt;# cAdvisor: http://localhost:8080&lt;/span&gt;

&lt;span class="c"&gt;# In Grafana:&lt;/span&gt;
&lt;span class="c"&gt;# 1. Add data source: Prometheus, URL = http://prometheus:9090&lt;/span&gt;
&lt;span class="c"&gt;# 2. Import dashboard: ID 893 (Docker monitoring)&lt;/span&gt;
&lt;span class="c"&gt;# 3. Watch container metrics in real time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Project 9: Complete CI/CD Pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Advanced | &lt;strong&gt;Stack:&lt;/strong&gt; GitHub Actions, Docker Hub, SSH Deploy | &lt;strong&gt;Concepts:&lt;/strong&gt; Automated build, test, push, deploy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI/CD Pipeline&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v*'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests in Docker&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;docker build --target test -t test-image .&lt;/span&gt;
          &lt;span class="s"&gt;docker run --rm test-image npm test&lt;/span&gt;

  &lt;span class="na"&gt;build-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest&lt;/span&gt;
            &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}/myapp:${{ github.sha }}&lt;/span&gt;
          &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;
          &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-push&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to server via SSH&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/ssh-action@v1.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_HOST }}&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;docker pull ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest&lt;/span&gt;
            &lt;span class="s"&gt;docker stop myapp || true&lt;/span&gt;
            &lt;span class="s"&gt;docker rm myapp || true&lt;/span&gt;
            &lt;span class="s"&gt;docker run -d \&lt;/span&gt;
              &lt;span class="s"&gt;--name myapp \&lt;/span&gt;
              &lt;span class="s"&gt;--restart unless-stopped \&lt;/span&gt;
              &lt;span class="s"&gt;-p 80:3000 \&lt;/span&gt;
              &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest&lt;/span&gt;
            &lt;span class="s"&gt;echo "Deployment complete!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Resume bullet — &lt;em&gt;"Implemented end-to-end CI/CD pipeline using GitHub Actions: automated Docker image build and test on every push, Docker Hub publish with semantic versioning, and zero-touch SSH deployment to production server."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Project 10: Full Observability Stack (ELK + Jaeger + Prometheus)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Advanced | &lt;strong&gt;Stack:&lt;/strong&gt; OpenTelemetry, Jaeger, Prometheus, ELK Stack | &lt;strong&gt;Concepts:&lt;/strong&gt; Metrics + Tracing + Logging — the three pillars of observability&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Your application&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
      &lt;span class="na"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://otel-collector:4318&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;otel-collector&lt;/span&gt;

  &lt;span class="c1"&gt;# OpenTelemetry Collector — central data routing hub&lt;/span&gt;
  &lt;span class="na"&gt;otel-collector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel/opentelemetry-collector-contrib&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./otel-config.yml:/etc/otelcol-contrib/config.yaml&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4318:4318"&lt;/span&gt;   &lt;span class="c1"&gt;# OTLP HTTP&lt;/span&gt;

  &lt;span class="c1"&gt;# Distributed tracing — request flows across services&lt;/span&gt;
  &lt;span class="na"&gt;jaeger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jaegertracing/all-in-one:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16686:16686"&lt;/span&gt;   &lt;span class="c1"&gt;# Jaeger UI&lt;/span&gt;

  &lt;span class="c1"&gt;# Metrics — numeric time-series data&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;

  &lt;span class="c1"&gt;# Dashboards — metrics visualization&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GF_SECURITY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin123&lt;/span&gt;

  &lt;span class="c1"&gt;# Logs — structured log storage&lt;/span&gt;
  &lt;span class="na"&gt;elasticsearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elasticsearch:8.11.0&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;discovery.type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;single-node&lt;/span&gt;
      &lt;span class="na"&gt;xpack.security.enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;es_data:/usr/share/elasticsearch/data&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9200:9200"&lt;/span&gt;

  &lt;span class="c1"&gt;# Log visualization&lt;/span&gt;
  &lt;span class="na"&gt;kibana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kibana:8.11.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5601:5601"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ELASTICSEARCH_HOSTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://elasticsearch:9200&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;es_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start full observability stack&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Wait for Elasticsearch to finish starting&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; elasticsearch | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"started"&lt;/span&gt;

&lt;span class="c"&gt;# Access points:&lt;/span&gt;
&lt;span class="c"&gt;# App:          http://localhost:3000&lt;/span&gt;
&lt;span class="c"&gt;# Jaeger UI:    http://localhost:16686  (request traces)&lt;/span&gt;
&lt;span class="c"&gt;# Prometheus:   http://localhost:9090   (raw metrics)&lt;/span&gt;
&lt;span class="c"&gt;# Grafana:      http://localhost:3001   (metric dashboards)&lt;/span&gt;
&lt;span class="c"&gt;# Kibana:       http://localhost:5601   (log analysis)&lt;/span&gt;

&lt;span class="c"&gt;# Generate some traffic&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..20&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;curl http://localhost:3000/api/users&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Resume bullet — &lt;em&gt;"Implemented complete observability stack using Docker Compose: OpenTelemetry instrumentation, Jaeger distributed tracing, Prometheus + Grafana metrics dashboards, and ELK stack log aggregation — full visibility into system behavior."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Projects Summary — Difficulty Progression
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Difficulty&lt;/th&gt;
&lt;th&gt;Key Skill&lt;/th&gt;
&lt;th&gt;Resume Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;React Portfolio&lt;/td&gt;
&lt;td&gt;Beginner&lt;/td&gt;
&lt;td&gt;Multi-stage build&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Node.js API&lt;/td&gt;
&lt;td&gt;Beginner&lt;/td&gt;
&lt;td&gt;Security, health checks&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Python FastAPI&lt;/td&gt;
&lt;td&gt;Beginner&lt;/td&gt;
&lt;td&gt;Python containerization&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;MERN Stack&lt;/td&gt;
&lt;td&gt;Intermediate&lt;/td&gt;
&lt;td&gt;Multi-service Compose&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;WordPress + MySQL&lt;/td&gt;
&lt;td&gt;Intermediate&lt;/td&gt;
&lt;td&gt;Health check conditions&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Microservices&lt;/td&gt;
&lt;td&gt;Intermediate&lt;/td&gt;
&lt;td&gt;API gateway pattern&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Size Optimization&lt;/td&gt;
&lt;td&gt;Intermediate&lt;/td&gt;
&lt;td&gt;Layer optimization&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Monitoring Stack&lt;/td&gt;
&lt;td&gt;Advanced&lt;/td&gt;
&lt;td&gt;Prometheus + Grafana&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;CI/CD Pipeline&lt;/td&gt;
&lt;td&gt;Advanced&lt;/td&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Observability Stack&lt;/td&gt;
&lt;td&gt;Advanced&lt;/td&gt;
&lt;td&gt;OTel + ELK + Jaeger&lt;/td&gt;
&lt;td&gt;Exceptional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Common Errors and Solutions
&lt;/h2&gt;

&lt;p&gt;The following errors are encountered by almost every Docker developer at some point. Each includes the root cause, solution, and prevention guidance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Cannot connect to the Docker daemon
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The Docker daemon (dockerd) has not started or has crashed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check daemon status&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status docker

&lt;span class="c"&gt;# Start the daemon&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker

&lt;span class="c"&gt;# Enable on boot&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;docker

&lt;span class="c"&gt;# Verify&lt;/span&gt;
docker info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Always enable the Docker service on boot with &lt;code&gt;systemctl enable docker&lt;/code&gt;. On Mac, ensure Docker Desktop is running before using the CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Permission denied on Docker socket
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The current user is not a member of the &lt;code&gt;docker&lt;/code&gt; group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add user to docker group&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;

&lt;span class="c"&gt;# Apply group change immediately (or log out and back in)&lt;/span&gt;
newgrp docker

&lt;span class="c"&gt;# Verify&lt;/span&gt;
docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Add the user to the docker group during initial Docker installation. In CI/CD pipelines, add the runner user to the docker group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Port is already allocated
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker: Error response from daemon: driver failed programming external connectivity on endpoint myapp: Bind for 0.0.0.0:8080 failed: port is already allocated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Host port 8080 is already in use by another process or container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find what is using the port&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;lsof &lt;span class="nt"&gt;-i&lt;/span&gt; :8080
&lt;span class="nb"&gt;sudo &lt;/span&gt;ss &lt;span class="nt"&gt;-tulpn&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; :8080

&lt;span class="c"&gt;# Kill the process (replace PID)&lt;/span&gt;
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; &amp;lt;PID&amp;gt;

&lt;span class="c"&gt;# OR use a different host port&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8081:80 nginx

&lt;span class="c"&gt;# Find running containers using the port&lt;/span&gt;
docker ps | &lt;span class="nb"&gt;grep &lt;/span&gt;8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Use environment variables for port configuration. Run &lt;code&gt;docker ps&lt;/code&gt; before starting new containers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: No space left on device
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: failed to solve: ... write /var/lib/docker/...: no space left on device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The Docker daemon's disk space is exhausted — old images, containers, and volumes have consumed all available space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check disk usage&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
docker system &lt;span class="nb"&gt;df&lt;/span&gt;

&lt;span class="c"&gt;# Full cleanup (removes everything unused)&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--volumes&lt;/span&gt;

&lt;span class="c"&gt;# Selective cleanup&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt;    &lt;span class="c"&gt;# Remove unused images&lt;/span&gt;
docker container prune   &lt;span class="c"&gt;# Remove stopped containers&lt;/span&gt;
docker volume prune      &lt;span class="c"&gt;# Remove unused volumes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Set up a cron job for &lt;code&gt;docker system prune&lt;/code&gt;. Monitor &lt;code&gt;/var/lib/docker&lt;/code&gt; size. Use multi-stage builds to keep images small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Container exits immediately
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The container's main process (CMD/ENTRYPOINT) completed, or it crashed with an error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check logs first&lt;/span&gt;
docker logs container_id
docker logs container_id &lt;span class="nt"&gt;--tail&lt;/span&gt; 50

&lt;span class="c"&gt;# Check exit code&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.ExitCode}}'&lt;/span&gt; container_id

&lt;span class="c"&gt;# Run interactively to debug&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; myapp bash

&lt;span class="c"&gt;# Bad: CMD nginx (exits immediately — starts as daemon)&lt;/span&gt;
&lt;span class="c"&gt;# Good: CMD ["nginx", "-g", "daemon off;"]&lt;/span&gt;

&lt;span class="c"&gt;# Override entrypoint for debugging&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; bash myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Use foreground commands in CMD. Test CMD locally first. Add proper health checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Pull access denied
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error response from daemon: pull access denied for myapp, repository does not exist or may require 'docker login'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The image name is incorrect, authentication is missing for a private registry, or the image does not exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Login to Docker Hub&lt;/span&gt;
docker login

&lt;span class="c"&gt;# Login to a private registry&lt;/span&gt;
docker login registry.mycompany.com &lt;span class="nt"&gt;-u&lt;/span&gt; username &lt;span class="nt"&gt;-p&lt;/span&gt; password

&lt;span class="c"&gt;# Check exact image name and tag&lt;/span&gt;
docker pull myapp:latest

&lt;span class="c"&gt;# Full path required for private registries&lt;/span&gt;
docker pull registry.mycompany.com/team/myapp:v1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Use full image paths. Store credentials in Docker credential stores, not plain text. Set up registry mirrors for CI/CD.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Build context is extremely large
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sending build context to Docker daemon  2.5GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; A &lt;code&gt;.dockerignore&lt;/code&gt; file is missing, so Docker sends the entire project directory (including &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, etc.) to the daemon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create .dockerignore file&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .dockerignore &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
node_modules/
dist/
build/
.git/
.env
.env.local
*.log
npm-debug.log*
.DS_Store
coverage/
.nyc_output
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Verify build context size after&lt;/span&gt;
docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Always create a &lt;code&gt;.dockerignore&lt;/code&gt; when starting a new Docker project. Commit it to version control alongside your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Docker Hub rate limit exceeded
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error response from daemon: toomanyrequests: You have reached your pull rate limit.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Docker Hub anonymous pull limit has been hit (100 pulls per 6 hours unauthenticated, 200 for free accounts).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Authenticate first to increase the limit&lt;/span&gt;
docker login
docker pull nginx

&lt;span class="c"&gt;# OR configure a registry mirror in /etc/docker/daemon.json:&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"registry-mirrors"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://mirror.gcr.io"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Restart Docker after configuration change&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Always authenticate in CI/CD. Use ECR or GCR as a pull-through cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Container OOMKilled — exit code 137
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The container exceeded its memory limit. The Linux OOM Killer forcefully terminated the process. Exit code 137 = 128 + 9 (SIGKILL).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if OOM killed&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].State.OOMKilled'&lt;/span&gt;

&lt;span class="c"&gt;# Check kernel logs for OOM&lt;/span&gt;
dmesg | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"oom&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;killed process"&lt;/span&gt;

&lt;span class="c"&gt;# Increase memory limit&lt;/span&gt;
docker run &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1g &lt;span class="nt"&gt;--memory-swap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2g myapp

&lt;span class="c"&gt;# Monitor memory usage&lt;/span&gt;
docker stats myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Profile application memory in development. Set realistic limits based on actual usage plus a 20% buffer. Add memory monitoring alerts in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: ECONNREFUSED connecting to database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: connect ECONNREFUSED 127.0.0.1:5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The application inside the container is using &lt;code&gt;127.0.0.1&lt;/code&gt; or &lt;code&gt;localhost&lt;/code&gt; to connect to the database. Inside a container, &lt;code&gt;localhost&lt;/code&gt; refers to the container itself — not another container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# WRONG - localhost inside container = the container itself&lt;/span&gt;
&lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost  &lt;span class="c"&gt;# ❌&lt;/span&gt;
&lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1  &lt;span class="c"&gt;# ❌&lt;/span&gt;

&lt;span class="c"&gt;# CORRECT - use the container name or service name&lt;/span&gt;
&lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres   &lt;span class="c"&gt;# ✅ (with docker compose)&lt;/span&gt;
&lt;span class="nv"&gt;DB_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;db         &lt;span class="c"&gt;# ✅ (compose service name)&lt;/span&gt;

&lt;span class="c"&gt;# Verify connectivity&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp ping postgres
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp nslookup postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Never hardcode localhost for inter-container communication. Use environment variables for hostnames. Use user-defined networks for DNS resolution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Volume mount permission denied
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Permission denied: '/app/data'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The container process user does not own the host volume directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option 1: Match user/group&lt;/span&gt;
docker run &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/data:/app/data myapp

&lt;span class="c"&gt;# Option 2: Fix host directory permissions&lt;/span&gt;
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 1000:1000 ./data

&lt;span class="c"&gt;# Option 3: Fix permissions in Dockerfile&lt;/span&gt;
RUN &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /app/data &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; node:node /app/data
USER node

&lt;span class="c"&gt;# Option 4: Use named volume (Docker manages permissions)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; myapp_data:/app/data myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error: DNS resolution failure between containers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: getaddrinfo ENOTFOUND api-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The default bridge network does not provide automatic DNS resolution between containers. Only user-defined networks include the embedded DNS server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a user-defined network&lt;/span&gt;
docker network create app-network

&lt;span class="c"&gt;# Run both containers on the same network&lt;/span&gt;
docker run &lt;span class="nt"&gt;--name&lt;/span&gt; api-service &lt;span class="nt"&gt;--network&lt;/span&gt; app-network myapi
docker run &lt;span class="nt"&gt;--name&lt;/span&gt; frontend &lt;span class="nt"&gt;--network&lt;/span&gt; app-network myfrontend

&lt;span class="c"&gt;# Now 'api-service' is resolvable by name&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;frontend curl http://api-service:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Always use user-defined networks for multi-container applications. Docker Compose sets this up automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: COPY file not found in build context
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COPY failed: file not found in build context or excluded by .dockerignore: stat package.json: file not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The file does not exist in the build context, &lt;code&gt;.dockerignore&lt;/code&gt; has excluded it, or the Dockerfile is being run from the wrong directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if file exists&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; package.json

&lt;span class="c"&gt;# Check .dockerignore isn't excluding it&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; .dockerignore | &lt;span class="nb"&gt;grep &lt;/span&gt;package

&lt;span class="c"&gt;# Build from the correct directory&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;   &lt;span class="c"&gt;# note the dot — build context = current directory&lt;/span&gt;

&lt;span class="c"&gt;# To build from a parent directory&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nt"&gt;-f&lt;/span&gt; ./subdir/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error: exec format error
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exec /docker-entrypoint.sh: exec format error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The image was built for a different CPU architecture (e.g., built on an M1 Mac for &lt;code&gt;arm64&lt;/code&gt;, then run on a &lt;code&gt;linux/amd64&lt;/code&gt; server).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check image platform&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].Architecture'&lt;/span&gt;

&lt;span class="c"&gt;# Build for a specific platform&lt;/span&gt;
docker build &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64 &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Build a multi-platform image using buildx&lt;/span&gt;
docker buildx create &lt;span class="nt"&gt;--use&lt;/span&gt;
docker buildx build &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64,linux/arm64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:latest &lt;span class="nt"&gt;--push&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Always use &lt;code&gt;--platform linux/amd64&lt;/code&gt; when building for production servers from an M1/M2 Mac.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: Cache keeps invalidating — npm install runs every time
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; &lt;code&gt;COPY . .&lt;/code&gt; is placed before &lt;code&gt;RUN npm install&lt;/code&gt;, so any file change invalidates the cache and forces &lt;code&gt;npm install&lt;/code&gt; to re-run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# WRONG - cache breaks on every file change&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .          # ❌ Any change = cache miss&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;   &lt;span class="c"&gt;# Re-runs every time&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;

&lt;span class="c"&gt;# CORRECT - npm install only re-runs when package.json changes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./      # ✅ Only copy dependency manifest&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci                 &lt;span class="c"&gt;# Only runs when package.json changes&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                   # Source code (frequently changing)&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Always separate dependency installation from source code copying. This is one of the most important Dockerfile optimizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: No configuration file provided (Docker Compose)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;no configuration file provided: not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Docker Compose cannot find a &lt;code&gt;docker-compose.yml&lt;/code&gt; or &lt;code&gt;compose.yml&lt;/code&gt; in the current directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check the current directory&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; compose

&lt;span class="c"&gt;# Navigate to the correct directory and run again&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your/project
docker compose up

&lt;span class="c"&gt;# OR specify the file explicitly&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; /path/to/docker-compose.yml up

&lt;span class="c"&gt;# Compose searches for these files in order:&lt;/span&gt;
&lt;span class="c"&gt;# 1. compose.yaml&lt;/span&gt;
&lt;span class="c"&gt;# 2. compose.yml&lt;/span&gt;
&lt;span class="c"&gt;# 3. docker-compose.yaml&lt;/span&gt;
&lt;span class="c"&gt;# 4. docker-compose.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error: Container status is unhealthy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The &lt;code&gt;HEALTHCHECK&lt;/code&gt; command is failing. The application has not properly started, or the health endpoint is not responding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inspect health check output&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .State.Health}}'&lt;/span&gt; myapp | jq &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# See the last 5 health check results&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].State.Health.Log'&lt;/span&gt;

&lt;span class="c"&gt;# Manually run the health check command&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost:3000/health

&lt;span class="c"&gt;# Temporarily disable to debug&lt;/span&gt;
docker run &lt;span class="nt"&gt;--no-healthcheck&lt;/span&gt; myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Prevention:&lt;/strong&gt; Test health check commands manually before adding them to the Dockerfile. Use &lt;code&gt;--start-period&lt;/code&gt; for applications that take time to start. Implement a &lt;code&gt;/health&lt;/code&gt; endpoint in your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error: OOMKilled on Python — ModuleNotFoundError
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ModuleNotFoundError: No module named 'flask'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; &lt;code&gt;requirements.txt&lt;/code&gt; was not copied correctly, or &lt;code&gt;pip install&lt;/code&gt; ran in the wrong directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Correct Dockerfile order&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app                           # Set WORKDIR first&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .                # Copy requirements&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt  &lt;span class="c"&gt;# Install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                               # Then copy code&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Debug: check if packages are installed&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp pip list | &lt;span class="nb"&gt;grep &lt;/span&gt;flask

&lt;span class="c"&gt;# Rebuild without cache&lt;/span&gt;
docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; myapp &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error: nginx bind permission denied on port 80
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; A non-root user cannot bind to privileged ports below 1024.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option 1: Use port 8080 (non-privileged)&lt;/span&gt;
&lt;span class="c"&gt;# In nginx.conf: listen 8080;&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 80:8080 nginx-custom

&lt;span class="c"&gt;# Option 2: Add NET_BIND_SERVICE capability&lt;/span&gt;
docker run &lt;span class="nt"&gt;--cap-add&lt;/span&gt; NET_BIND_SERVICE &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 nginx-custom

&lt;span class="c"&gt;# Option 3: Use the nginx-unprivileged image (uses port 8080 by default)&lt;/span&gt;
FROM nginxinc/nginx-unprivileged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; When using a non-root user with nginx, always configure &lt;code&gt;nginx.conf&lt;/code&gt; to listen on port 8080 or higher. Use &lt;code&gt;nginxinc/nginx-unprivileged&lt;/code&gt; as the base image for a secure, non-root nginx setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;docker logs&lt;/code&gt; and &lt;code&gt;docker inspect&lt;/code&gt; are your best debugging tools. 90% of Docker errors can be diagnosed with just these two commands.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Debugging Methodology
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Systematic Debugging — Step by Step
&lt;/h3&gt;

&lt;p&gt;When something stops working, follow this systematic checklist. Do not make random changes without first diagnosing the problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Approach Docker debugging like a doctor: check symptoms first, then diagnose, then treat. Never make changes without a clear diagnosis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 1: Is the Container Running?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Show running containers&lt;/span&gt;
docker ps

&lt;span class="c"&gt;# Show ALL containers (including stopped)&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;# Pretty format with useful columns&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"table {{.Names}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Status}}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;{{.Ports}}"&lt;/span&gt;

&lt;span class="c"&gt;# Filter by container name&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp
&lt;span class="c"&gt;# Check the STATUS column: "Exited (1) 2 minutes ago" = crashed&lt;/span&gt;
&lt;span class="c"&gt;# "Exited (0) 2 minutes ago" = completed normally&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: What Do the Logs Say?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Last 100 lines of logs&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 100 myapp

&lt;span class="c"&gt;# Follow logs in real time&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; myapp

&lt;span class="c"&gt;# Logs from the last 1 hour&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; 1h myapp

&lt;span class="c"&gt;# Logs with timestamps&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-t&lt;/span&gt; myapp

&lt;span class="c"&gt;# Filter for errors&lt;/span&gt;
docker logs myapp 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"error&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;fatal&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;critical"&lt;/span&gt;

&lt;span class="c"&gt;# Logs persist even for stopped containers until the container is removed&lt;/span&gt;
docker logs myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: What Is the Exit Code?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get exit code&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.ExitCode}}'&lt;/span&gt; myapp

&lt;span class="c"&gt;# Get full state information&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .State}}'&lt;/span&gt; myapp | jq &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Exit code reference:&lt;/span&gt;
&lt;span class="c"&gt;# 0   = Normal exit (success, or CMD completed)&lt;/span&gt;
&lt;span class="c"&gt;# 1   = General error (application error)&lt;/span&gt;
&lt;span class="c"&gt;# 2   = Misuse of shell command&lt;/span&gt;
&lt;span class="c"&gt;# 125 = Docker daemon error&lt;/span&gt;
&lt;span class="c"&gt;# 126 = Container command cannot be invoked&lt;/span&gt;
&lt;span class="c"&gt;# 127 = Container command not found&lt;/span&gt;
&lt;span class="c"&gt;# 130 = Container terminated by Ctrl+C (SIGINT)&lt;/span&gt;
&lt;span class="c"&gt;# 137 = SIGKILL (OOM kill or docker kill)&lt;/span&gt;
&lt;span class="c"&gt;# 139 = Segmentation fault&lt;/span&gt;
&lt;span class="c"&gt;# 143 = SIGTERM (graceful shutdown)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Can You Enter the Container?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enter a running container (bash for ubuntu/debian)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp bash

&lt;span class="c"&gt;# For Alpine Linux (no bash by default)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; myapp sh

&lt;span class="c"&gt;# Run specific commands inside&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /app
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp &lt;span class="nb"&gt;cat&lt;/span&gt; /app/config.json
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp &lt;span class="nb"&gt;env&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;DB_

&lt;span class="c"&gt;# If the container keeps crashing, override entrypoint to debug&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; bash myapp
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; sh myapp  &lt;span class="c"&gt;# Alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Check Resource Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Live stats for a specific container&lt;/span&gt;
docker stats myapp

&lt;span class="c"&gt;# Live stats for all containers&lt;/span&gt;
docker stats

&lt;span class="c"&gt;# One-time snapshot (no streaming)&lt;/span&gt;
docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt;

&lt;span class="c"&gt;# Check process list inside container&lt;/span&gt;
docker top myapp

&lt;span class="c"&gt;# Check resource limits set on container&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .HostConfig.Memory}}'&lt;/span&gt; myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Network Inspection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inspect a network&lt;/span&gt;
docker network inspect app-network

&lt;span class="c"&gt;# Check container's network settings&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .NetworkSettings}}'&lt;/span&gt; myapp | jq &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Test DNS resolution from inside the container&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp nslookup postgres
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp nslookup google.com

&lt;span class="c"&gt;# Test connectivity&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp curl &lt;span class="nt"&gt;-v&lt;/span&gt; http://api-service:3000/health
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp ping &lt;span class="nt"&gt;-c&lt;/span&gt; 3 postgres

&lt;span class="c"&gt;# Check which networks the container is on&lt;/span&gt;
docker inspect &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{json .NetworkSettings.Networks}}'&lt;/span&gt; myapp | jq &lt;span class="s1"&gt;'keys'&lt;/span&gt;

&lt;span class="c"&gt;# Show port mappings&lt;/span&gt;
docker port myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7: Check Events
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# All events in the last hour&lt;/span&gt;
docker events &lt;span class="nt"&gt;--since&lt;/span&gt; 1h

&lt;span class="c"&gt;# Filter by container&lt;/span&gt;
docker events &lt;span class="nt"&gt;--since&lt;/span&gt; 1h &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;container&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp

&lt;span class="c"&gt;# Filter by event type&lt;/span&gt;
docker events &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;container &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oom

&lt;span class="c"&gt;# Watch events live&lt;/span&gt;
docker events

&lt;span class="c"&gt;# Events by image&lt;/span&gt;
docker events &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 8: Full Configuration Inspect
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full JSON inspection&lt;/span&gt;
docker inspect myapp | jq &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Specific fields&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].Config.Env'&lt;/span&gt;        &lt;span class="c"&gt;# Environment variables&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].Mounts'&lt;/span&gt;             &lt;span class="c"&gt;# Volume mounts&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].HostConfig'&lt;/span&gt;         &lt;span class="c"&gt;# Host config (ports, limits)&lt;/span&gt;
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].NetworkSettings'&lt;/span&gt;    &lt;span class="c"&gt;# Network information&lt;/span&gt;

&lt;span class="c"&gt;# Image inspection&lt;/span&gt;
docker inspect myimage:tag | jq &lt;span class="s1"&gt;'.[0].Config.Cmd'&lt;/span&gt;
docker inspect myimage:tag | jq &lt;span class="s1"&gt;'.[0].Config.Entrypoint'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Do not include debugging tools in production images — they increase image size and expand the attack surface. Instead, maintain a separate debug image built from your production image plus debugging tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Quick Diagnosis Reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem Symptom&lt;/th&gt;
&lt;th&gt;First Command&lt;/th&gt;
&lt;th&gt;Common Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Container not running&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker ps -a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check exit code, check logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App not responding&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker logs myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Look for errors in startup output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Containers cannot communicate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker network inspect&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add both to the same user-defined network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data not persisting&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docker inspect&lt;/code&gt; (Mounts section)&lt;/td&gt;
&lt;td&gt;Add a volume mount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High memory usage&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker stats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;--memory&lt;/code&gt; limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow builds&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker build --no-cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix Dockerfile layer order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port not accessible&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker port myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;-p&lt;/code&gt; flag, check binding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container keeps restarting&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker logs myapp --tail 20&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fix application error, check exit code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Common Docker Interview Questions
&lt;/h2&gt;

&lt;p&gt;The following 15 questions cover the most important Docker topics you will encounter in engineering interviews. Each includes a concise answer, a detailed explanation, and common pitfalls to avoid.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Q1. What is the difference between a Docker image and a container?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Docker image is an immutable, read-only template made of layered filesystems containing everything needed to run an application. A container is a running instance of that image — it adds a thin writable layer on top. Multiple containers can run from the same image simultaneously.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key point:&lt;/strong&gt; Image = blueprint (immutable). Container = running instance (has a writable layer).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red flag:&lt;/strong&gt; Saying "image and container are the same thing" immediately signals a foundational misunderstanding.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Q2. How does Docker image layer caching work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Docker caches each instruction's result as a layer. On rebuild, if an instruction and its inputs are unchanged, Docker reuses the cached layer and skips re-execution. Once any layer is invalidated, all subsequent layers are rebuilt. This is why Dockerfile instruction order matters — place rarely-changing instructions (install dependencies) before frequently-changing ones (copy source code).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Good - npm install only re-runs when package.json changes&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .   # source changes only invalidate this layer and below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red flag:&lt;/strong&gt; Not structuring Dockerfiles to exploit layer caching. A 3-minute &lt;code&gt;npm install&lt;/code&gt; running on every CI build is an immediate code review concern.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Q3. What are multi-stage builds and why use them?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multi-stage builds use multiple &lt;code&gt;FROM&lt;/code&gt; statements in one Dockerfile. You compile/build in a full environment, then &lt;code&gt;COPY --from=stage&lt;/code&gt; only the compiled artifacts into a minimal runtime image. This keeps build tools out of the final image, dramatically reducing size and attack surface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; server .

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;scratch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/server /server&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/server"]&lt;/span&gt;
&lt;span class="c"&gt;# Result: ~10 MB image vs ~800 MB with full Go toolchain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red flag:&lt;/strong&gt; Not knowing that you can have more than two stages, or that you can target specific stages with &lt;code&gt;docker build --target=stagename&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Q4. What is the difference between CMD and ENTRYPOINT?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ENTRYPOINT&lt;/code&gt; sets the fixed executable that always runs. &lt;code&gt;CMD&lt;/code&gt; provides default arguments that can be overridden at runtime. Together: &lt;code&gt;ENTRYPOINT&lt;/code&gt; is "what to run" and &lt;code&gt;CMD&lt;/code&gt; is "with what default arguments." &lt;code&gt;CMD&lt;/code&gt; alone is fully overridable by passing arguments to &lt;code&gt;docker run&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]   # always runs python app.py&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["--port", "8080"]             # default arg, overridable&lt;/span&gt;

&lt;span class="c"&gt;# docker run myapp                  → python app.py --port 8080&lt;/span&gt;
&lt;span class="c"&gt;# docker run myapp --port 9090      → python app.py --port 9090&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red flag:&lt;/strong&gt; Using shell form for CMD/ENTRYPOINT (&lt;code&gt;CMD node server.js&lt;/code&gt;). Shell form runs your command via &lt;code&gt;/bin/sh -c&lt;/code&gt;, meaning the shell is PID 1 — not your application. SIGTERM is not forwarded, breaking graceful shutdown.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Q5. What is the difference between volumes and bind mounts?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bind mounts link a specific host path into the container — you control the location. Named volumes are managed by Docker, stored at &lt;code&gt;/var/lib/docker/volumes/&lt;/code&gt;. Named volumes are preferred in production for persistent data (databases). Bind mounts are preferred in development for live code reloading.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Named Volume&lt;/td&gt;
&lt;td&gt;Docker-managed&lt;/td&gt;
&lt;td&gt;DB data, persistent app data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind Mount&lt;/td&gt;
&lt;td&gt;Specific host path&lt;/td&gt;
&lt;td&gt;Development hot reload, config files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tmpfs&lt;/td&gt;
&lt;td&gt;Host RAM (never on disk)&lt;/td&gt;
&lt;td&gt;Secrets, temporary cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red flag:&lt;/strong&gt; Using bind mounts in production — they create host-container coupling and can expose host files if misconfigured.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Q6. Explain Docker networking — bridge, host, and overlay.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;bridge&lt;/strong&gt; (default): Isolated virtual network on the host. Containers communicate via container names on user-defined bridge networks (with automatic DNS). Requires &lt;code&gt;-p&lt;/code&gt; to expose ports to the host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;host&lt;/strong&gt;: Container shares the host's network stack directly. No NAT overhead, but no network isolation. Only works on Linux.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;overlay&lt;/strong&gt;: Spans multiple Docker hosts using VXLAN tunneling. Required for Docker Swarm multi-host communication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default bridge network does not provide automatic DNS — containers must use IPs. User-defined bridge networks resolve container names via Docker's embedded DNS at &lt;code&gt;127.0.0.11&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red flag:&lt;/strong&gt; Using the default bridge network in production. Default bridge has no DNS and all containers on it can communicate with each other — no isolation.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Q7. What are Dockerfile best practices?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Key best practices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use minimal base images (alpine, distroless)&lt;/li&gt;
&lt;li&gt;Use multi-stage builds for compiled languages and build-step frameworks&lt;/li&gt;
&lt;li&gt;Exploit layer caching — copy dependency manifests before source code&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;.dockerignore&lt;/code&gt; to exclude &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run as a non-root user (&lt;code&gt;USER node&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Use exec form for CMD and ENTRYPOINT (&lt;code&gt;["node", "server.js"]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Combine &lt;code&gt;RUN&lt;/code&gt; commands to minimize layers, clean up in the same instruction&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;HEALTHCHECK&lt;/code&gt; for production images&lt;/li&gt;
&lt;li&gt;Pin base image versions — avoid &lt;code&gt;:latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Never pass secrets via &lt;code&gt;ARG&lt;/code&gt; or &lt;code&gt;ENV&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Q8. What does &lt;code&gt;depends_on&lt;/code&gt; do in Docker Compose — does it guarantee the service is ready?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;depends_on&lt;/code&gt; controls startup order — it ensures the dependent container starts before the dependent service. By default it only waits for the container to start (&lt;code&gt;service_started&lt;/code&gt;), not for the application inside to be ready.&lt;/p&gt;

&lt;p&gt;To actually wait for a service to be ready, combine &lt;code&gt;depends_on&lt;/code&gt; with &lt;code&gt;condition: service_healthy&lt;/code&gt; and a proper &lt;code&gt;healthcheck&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;   &lt;span class="c1"&gt;# waits for healthcheck to pass&lt;/span&gt;

&lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
  &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;postgres"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red flag:&lt;/strong&gt; Using &lt;code&gt;depends_on&lt;/code&gt; without &lt;code&gt;condition: service_healthy&lt;/code&gt; and then being surprised when the application crashes on startup because the database was not ready.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Q9. How do you secure a Docker container in production?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A hardened container uses multiple layers of defense:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user&lt;/span&gt; 1000:1000 &lt;span class="se"&gt;\ &lt;/span&gt;             &lt;span class="c"&gt;# non-root user&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-drop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ALL &lt;span class="se"&gt;\ &lt;/span&gt;               &lt;span class="c"&gt;# drop all capabilities&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NET_BIND_SERVICE &lt;span class="se"&gt;\ &lt;/span&gt;   &lt;span class="c"&gt;# add only what is needed&lt;/span&gt;
  &lt;span class="nt"&gt;--read-only&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;                  &lt;span class="c"&gt;# read-only root filesystem&lt;/span&gt;
  &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp:size&lt;span class="o"&gt;=&lt;/span&gt;50m &lt;span class="se"&gt;\ &lt;/span&gt;        &lt;span class="c"&gt;# writable RAM-backed temp&lt;/span&gt;
  &lt;span class="nt"&gt;--no-new-privileges&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;          &lt;span class="c"&gt;# prevent privilege escalation&lt;/span&gt;
  &lt;span class="nt"&gt;--security-opt&lt;/span&gt; &lt;span class="nv"&gt;seccomp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/docker/seccomp.json &lt;span class="se"&gt;\&lt;/span&gt;
  myapp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checklist: non-root USER, no &lt;code&gt;--privileged&lt;/code&gt;, &lt;code&gt;--read-only&lt;/code&gt; filesystem, drop all capabilities, minimal base image, no &lt;code&gt;docker.sock&lt;/code&gt; mount, scan with Trivy, use BuildKit secret mounts (never &lt;code&gt;ARG&lt;/code&gt;/&lt;code&gt;ENV&lt;/code&gt; for secrets).&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Q10. What is the difference between &lt;code&gt;docker stop&lt;/code&gt; and &lt;code&gt;docker kill&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker stop&lt;/code&gt; sends SIGTERM to allow graceful shutdown, waits 10 seconds (configurable), then sends SIGKILL. &lt;code&gt;docker kill&lt;/code&gt; immediately sends SIGKILL without waiting.&lt;/p&gt;

&lt;p&gt;Graceful shutdown matters: SIGTERM lets applications finish active requests, close database connections, and flush logs. Applications should handle SIGTERM explicitly. Always use &lt;code&gt;docker stop&lt;/code&gt; in production scripts — &lt;code&gt;docker kill&lt;/code&gt; can cause data corruption if the app is mid-write.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Q11. What is BuildKit and what advantages does it provide?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;BuildKit is Docker's next-generation build engine (default since Docker 23.0). Key advantages over the classic builder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parallel stage execution in multi-stage builds&lt;/li&gt;
&lt;li&gt;Improved layer caching (including registry-based cache)&lt;/li&gt;
&lt;li&gt;Secret mounts (&lt;code&gt;RUN --mount=type=secret&lt;/code&gt;) — secrets are never stored in any layer&lt;/li&gt;
&lt;li&gt;SSH agent forwarding for private repositories&lt;/li&gt;
&lt;li&gt;Multi-platform builds with buildx&lt;/li&gt;
&lt;li&gt;Significantly faster build times
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# BuildKit secret mount (secret is never baked into image)&lt;/span&gt;
RUN &lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret,id&lt;span class="o"&gt;=&lt;/span&gt;npmrc &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;cp&lt;/span&gt; /run/secrets/npmrc ~/.npmrc &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; ~/.npmrc

docker build &lt;span class="nt"&gt;--secret&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;npmrc,src&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.npmrc &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red flag:&lt;/strong&gt; Not knowing BuildKit secret mounts exist, and instead passing secrets via &lt;code&gt;ARG&lt;/code&gt; or &lt;code&gt;ENV&lt;/code&gt;, which appear in &lt;code&gt;docker history&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Q12. How do you implement zero-downtime deployments with Docker?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use blue-green or rolling update strategies. The key principle: start the new container and verify it is healthy before stopping the old one — never the reverse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Blue-green pattern&lt;/span&gt;
&lt;span class="c"&gt;# 1. Start new (green) container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; myapp-green &lt;span class="nt"&gt;--network&lt;/span&gt; app myapp:new

&lt;span class="c"&gt;# 2. Health check green&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp-green curl &lt;span class="nt"&gt;-sf&lt;/span&gt; http://localhost:3000/health

&lt;span class="c"&gt;# 3. Switch traffic (update nginx upstream, then reload)&lt;/span&gt;
&lt;span class="c"&gt;# nginx -s reload&lt;/span&gt;

&lt;span class="c"&gt;# 4. Stop old (blue)&lt;/span&gt;
docker stop myapp-blue &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;rm &lt;/span&gt;myapp-blue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Kubernetes, use rolling updates with &lt;code&gt;maxUnavailable: 0&lt;/code&gt; and &lt;code&gt;maxSurge: 1&lt;/code&gt; combined with readiness probes.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Q13. How do you handle secrets in Docker?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Never store secrets in &lt;code&gt;ARG&lt;/code&gt;, &lt;code&gt;ENV&lt;/code&gt;, or files copied into an image. The correct approaches are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build-time secrets:&lt;/strong&gt; Use BuildKit &lt;code&gt;--mount=type=secret&lt;/code&gt; — the secret is available during the build RUN instruction but is never stored in any image layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime secrets:&lt;/strong&gt; Inject via environment variables at &lt;code&gt;docker run&lt;/code&gt; time (values are not in the image), Docker Compose secrets (mounted as files at &lt;code&gt;/run/secrets/&lt;/code&gt;), or external secret managers (AWS Secrets Manager, HashiCorp Vault).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify: &lt;code&gt;docker history myimage&lt;/code&gt; and &lt;code&gt;docker inspect myimage&lt;/code&gt; should never reveal secret values.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Q14. What is the difference between &lt;code&gt;docker exec&lt;/code&gt; and &lt;code&gt;docker attach&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker exec&lt;/code&gt; starts a new process inside a running container — it is safe for debugging. &lt;code&gt;docker attach&lt;/code&gt; connects your terminal to the container's existing PID 1 process. If you press Ctrl+C while attached, you send SIGINT to PID 1 and stop the container.&lt;/p&gt;

&lt;p&gt;Always use &lt;code&gt;docker exec -it mycontainer /bin/sh&lt;/code&gt; for debugging. Use &lt;code&gt;docker attach&lt;/code&gt; only when you specifically need to interact with PID 1, and detach with Ctrl+P, Ctrl+Q.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Q15. How do you optimize Docker builds in CI/CD for speed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Five key techniques:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Registry-based layer caching&lt;/strong&gt; — reuse layers from previous builds with &lt;code&gt;--cache-from&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dockerfile layer order&lt;/strong&gt; — dependency installation before source code copy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BuildKit inline cache&lt;/strong&gt; — export cache to registry with &lt;code&gt;--cache-to type=registry,mode=max&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-stage parallelism&lt;/strong&gt; — BuildKit builds independent stages in parallel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions/GitLab cache&lt;/strong&gt; — use &lt;code&gt;type=gha&lt;/code&gt; cache for persistent caching between runs
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions example&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;
    &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A well-optimized pipeline can reduce build times from 10 minutes to under 2 minutes.&lt;/p&gt;

&lt;p&gt;Prepping for interviews? See &lt;a href="https://opscanopy.com/learn/guides/docker-interview-questions/" rel="noopener noreferrer"&gt;Docker Interview Questions&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete Docker Cheat Sheet
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Container Commands
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -d -p 8080:80 --name app nginx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run detached with port mapping and name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -it --rm ubuntu bash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Interactive shell, auto-remove on exit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -e ENV=prod --env-file .env app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;With environment variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -v vol:/data --memory=512m app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;With volume and memory limit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker ps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List running containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker ps -a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List ALL containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Custom format output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker stop myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Graceful stop (SIGTERM then SIGKILL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker kill myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Immediate kill (SIGKILL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker rm myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove stopped container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker rm -f myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Force remove running container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker logs -f --tail 100 myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Follow last 100 log lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker exec -it myapp bash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enter container shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker exec myapp cat /app/config.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run command in container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker cp myapp:/app/file.txt ./&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy file from container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker cp ./file.txt myapp:/app/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy file to container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker stats --no-stream&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Snapshot resource usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker top myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show processes in container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker inspect myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full container JSON information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker inspect --format '{{.NetworkSettings.IPAddress}}' myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get container IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker update --restart=always --memory=1g myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Update running container settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker rename myapp newname&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rename container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker pause myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Freeze container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker unpause myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unfreeze container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker container prune&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove all stopped containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker events --filter type=container&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Live container events&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Image Commands
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker pull nginx:1.25-alpine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull specific tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker pull ubuntu@sha256:abc123&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull by digest (immutable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker images&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List local images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker build -t myapp:v1.0 .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build from Dockerfile in current directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker build -f Dockerfile.prod -t myapp:prod .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use specific Dockerfile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker build --no-cache -t myapp .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Force fresh build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker build --build-arg NODE_ENV=prod -t myapp .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;With build argument&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker build --target production -t myapp .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build specific stage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker tag myapp:latest registry/myapp:v1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tag for registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker push registry/myapp:v1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Push to registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker rmi myapp:latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker history myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show image layers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker history --no-trunc myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full layer commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker inspect myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Image JSON details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker save -o myapp.tar myapp:latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Export to tar archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker load -i myapp.tar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Import from tar archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker image prune&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove dangling images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker image prune -a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove all unused images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker system df&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disk usage summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker system prune -af --volumes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full cleanup of all unused resources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Network Commands
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker network ls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List networks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker network create mynet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create bridge network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker network create --internal secure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internal network (no internet access)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker network inspect mynet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker network connect mynet mycontainer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Connect running container to network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker network disconnect mynet mycontainer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Disconnect container from network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker network rm mynet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker network prune&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove unused networks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run --network mynet myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run in specific network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run --network host myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use host network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run --network none myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No networking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -p 8080:80 myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Publish port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -p 127.0.0.1:8080:80 myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bind to localhost only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker port myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show port mappings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Volume Commands
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker volume create mydata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create named volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker volume ls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List volumes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker volume inspect mydata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Volume details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker volume rm mydata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker volume prune&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove unused volumes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -v mydata:/app/data myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mount named volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -v $(pwd):/app myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bind mount current directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -v /app/node_modules myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anonymous volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run --mount type=volume,source=mydata,target=/data myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mount syntax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run --mount type=bind,source=$(pwd),target=/app myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bind mount syntax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run --mount type=tmpfs,target=/tmp myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tmpfs mount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run -v /host/path:/container/path:ro myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read-only mount&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start all services detached&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose up -d --build&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rebuild and start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose up -d --scale api=3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start with 3 api replicas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose down&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop and remove containers and networks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose down -v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Also remove volumes (destructive — use with care)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose ps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose logs -f api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Follow api logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose exec api bash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shell into running service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose run --rm api npm test&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One-off command in new container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose build --no-cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Force rebuild&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose pull&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pull latest images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose config&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validate and show resolved configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose restart api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restart specific service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose --profile dev up -d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start with profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose -f base.yml -f prod.yml up -d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Multiple compose files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Dockerfile Quick Reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instruction&lt;/th&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FROM node:18-alpine AS builder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LABEL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LABEL version="1.0" maintainer="x@y.com"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RUN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get install -y curl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execute during build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CMD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMD ["node", "server.js"]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Default command (overridable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ENTRYPOINT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ENTRYPOINT ["npm", "start"]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fixed executable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COPY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;COPY --chown=node:node . /app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy files from build context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ADD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ADD archive.tar.gz /app/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy and auto-extract archives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WORKDIR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WORKDIR /app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set working directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ENV&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ENV NODE_ENV=production PORT=3000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runtime environment variable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ARG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ARG NODE_VERSION=18&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build-time variable only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXPOSE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EXPOSE 3000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Document port (does not publish)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;VOLUME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VOLUME /app/data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declare mount point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;USER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;USER node&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Switch to non-root user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HEALTHCHECK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HEALTHCHECK --interval=30s CMD curl -f http://localhost/health&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Health monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STOPSIGNAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;STOPSIGNAL SIGQUIT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Override stop signal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Power One-Liners
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;One-liner&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker system prune -af --volumes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Complete cleanup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker stop $(docker ps -q)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stop all running containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker rm $(docker ps -aq)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove all containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker rmi $(docker images -q)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove all images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker rm $(docker ps -aq -f status=exited)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove only exited containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker inspect --format '{{.NetworkSettings.IPAddress}}' myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get container IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker inspect --format '{{.State.ExitCode}}' myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get exit code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`docker logs myapp 2&amp;gt;&amp;amp;1 \&lt;/td&gt;
&lt;td&gt;grep -i error`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker logs --since 1h --tail 100 myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recent logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker events --filter 'event=die'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Watch for container deaths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" \&lt;/td&gt;
&lt;td&gt;sort -h`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run --rm -v $(pwd):/app node:18-alpine npm install&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run npm install via Docker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker exec myapp env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List container environment variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker diff myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show filesystem changes in container&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Production Workflows
&lt;/h2&gt;

&lt;p&gt;Real-world Docker workflows used daily in production environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Daily DevOps Docker Checklist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ===== Morning Health Check =====&lt;/span&gt;
docker ps                                    &lt;span class="c"&gt;# Are all containers running?&lt;/span&gt;
docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt;                     &lt;span class="c"&gt;# Is resource usage normal?&lt;/span&gt;
docker logs myapp &lt;span class="nt"&gt;--since&lt;/span&gt; 8h | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'error|fatal|panic'&lt;/span&gt;  &lt;span class="c"&gt;# Overnight errors?&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /var/lib/docker                        &lt;span class="c"&gt;# Is disk space OK?&lt;/span&gt;

&lt;span class="c"&gt;# ===== Deployment Workflow =====&lt;/span&gt;
&lt;span class="c"&gt;# 1. Build&lt;/span&gt;
&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git describe &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nt"&gt;--always&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myapp:&lt;span class="nv"&gt;$VERSION&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# 2. Test&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; myapp:&lt;span class="nv"&gt;$VERSION&lt;/span&gt; npm &lt;span class="nb"&gt;test&lt;/span&gt;

&lt;span class="c"&gt;# 3. Tag and push&lt;/span&gt;
docker tag myapp:&lt;span class="nv"&gt;$VERSION&lt;/span&gt; registry/myapp:&lt;span class="nv"&gt;$VERSION&lt;/span&gt;
docker push registry/myapp:&lt;span class="nv"&gt;$VERSION&lt;/span&gt;

&lt;span class="c"&gt;# 4. Deploy (rolling update on server)&lt;/span&gt;
docker pull registry/myapp:&lt;span class="nv"&gt;$VERSION&lt;/span&gt;
docker stop myapp-old &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;docker rename myapp myapp-old &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; myapp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 80:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  registry/myapp:&lt;span class="nv"&gt;$VERSION&lt;/span&gt;

&lt;span class="c"&gt;# 5. Verify&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;5 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker ps | &lt;span class="nb"&gt;grep &lt;/span&gt;myapp
curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost:80/health

&lt;span class="c"&gt;# 6. Clean up old container&lt;/span&gt;
docker &lt;span class="nb"&gt;rm &lt;/span&gt;myapp-old
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rollback Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PREV_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"v1.2.2"&lt;/span&gt;
docker pull registry/myapp:&lt;span class="nv"&gt;$PREV_VERSION&lt;/span&gt;
docker stop myapp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;rm &lt;/span&gt;myapp
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; myapp registry/myapp:&lt;span class="nv"&gt;$PREV_VERSION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Never run &lt;code&gt;docker rmi&lt;/code&gt; on the last known-good image until the new deployment has been stable for at least 30 minutes. Tag the last working image as &lt;code&gt;:stable&lt;/code&gt; or &lt;code&gt;:prev&lt;/code&gt; to enable instant rollback.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Monthly Maintenance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clean up unused resources older than 30 days&lt;/span&gt;
docker system prune &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="s2"&gt;"until=720h"&lt;/span&gt;
docker volume prune &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Update base images and rebuild with latest security patches&lt;/span&gt;
docker pull node:18-alpine
docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Zero-Downtime Deployment Pattern
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# zero-downtime-deploy.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nv"&gt;NEW_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"registry/myapp:&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CONTAINER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"myapp"&lt;/span&gt;
&lt;span class="nv"&gt;BLUE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONTAINER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-blue"&lt;/span&gt;
&lt;span class="nv"&gt;GREEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONTAINER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-green"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Pulling new image..."&lt;/span&gt;
docker pull &lt;span class="nv"&gt;$NEW_IMAGE&lt;/span&gt;

&lt;span class="c"&gt;# Determine which container is active&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;docker ps | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nv"&gt;$BLUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;ACTIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$BLUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;INACTIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GREEN&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nv"&gt;ACTIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GREEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;INACTIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$BLUE&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting &lt;/span&gt;&lt;span class="nv"&gt;$INACTIVE&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$INACTIVE&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; app &lt;span class="nv"&gt;$NEW_IMAGE&lt;/span&gt;

&lt;span class="c"&gt;# Health check loop&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..30&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if &lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nv"&gt;$INACTIVE&lt;/span&gt; curl &lt;span class="nt"&gt;-sf&lt;/span&gt; http://localhost:3000/health&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Health check passed!"&lt;/span&gt;
    &lt;span class="nb"&gt;break
  &lt;/span&gt;&lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;2
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Switch traffic (update nginx/load balancer upstream here)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Switching traffic to &lt;/span&gt;&lt;span class="nv"&gt;$INACTIVE&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;

&lt;span class="c"&gt;# Stop old container&lt;/span&gt;
docker stop &lt;span class="nv"&gt;$ACTIVE&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nv"&gt;$ACTIVE&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deployment complete! Active: &lt;/span&gt;&lt;span class="nv"&gt;$INACTIVE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Incident Response Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container crashed — investigate&lt;/span&gt;
docker logs myapp &lt;span class="nt"&gt;--tail&lt;/span&gt; 200
docker inspect myapp | jq &lt;span class="s1"&gt;'.[0].State'&lt;/span&gt;  &lt;span class="c"&gt;# Check exit code, OOMKilled flag&lt;/span&gt;
docker events &lt;span class="nt"&gt;--since&lt;/span&gt; 1h

&lt;span class="c"&gt;# High CPU or Memory&lt;/span&gt;
docker stats &lt;span class="nt"&gt;--no-stream&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp top

&lt;span class="c"&gt;# Network issues&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;myapp curl &lt;span class="nt"&gt;-v&lt;/span&gt; http://db:5432
docker network inspect myapp_default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Docker Compose Patterns
&lt;/h2&gt;

&lt;p&gt;Battle-tested Compose patterns used in real production environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Base + Override (Development vs Production)
&lt;/h3&gt;

&lt;p&gt;The most common pattern — one base file with environment-specific overrides.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml (base — committed to git)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${REGISTRY:-myapp}/api:${TAG:-latest}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${DB_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pgdata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;/var/lib/postgresql/data&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

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

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.override.yml (development overrides — add to .gitignore)&lt;/span&gt;
&lt;span class="c1"&gt;# Auto-loaded when you run: docker compose up&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;                          &lt;span class="c1"&gt;# Build locally instead of pulling&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;                        &lt;span class="c1"&gt;# Live code reload&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/app/node_modules&lt;/span&gt;             &lt;span class="c1"&gt;# Preserve installed node_modules&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DEBUG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://user:pass@db:5432/myapp&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run dev&lt;/span&gt;              &lt;span class="c1"&gt;# Use dev server with hot reload&lt;/span&gt;
  &lt;span class="c1"&gt;# Development-only extras&lt;/span&gt;
  &lt;span class="na"&gt;mailhog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mailhog/mailhog&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8025:8025"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;             &lt;span class="c1"&gt;# Fake email server for testing&lt;/span&gt;
  &lt;span class="na"&gt;redis-ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rediscommander/redis-commander&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8081:8081"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;                 &lt;span class="c1"&gt;# Only starts with --profile debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.prod.yml (production overrides)&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
        &lt;span class="na"&gt;reservations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;cpus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.25"&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json-file&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10m"&lt;/span&gt;
        &lt;span class="na"&gt;max-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-f"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000/health"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;40s&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.25-alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx/prod.conf:/etc/nginx/conf.d/default.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ssl:/etc/ssl:ro&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Development (auto-loads override file)&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Production&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.prod.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Validate the merged configuration&lt;/span&gt;
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.prod.yml config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Microservices with Profiles
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Core services (always start)&lt;/span&gt;
  &lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp/gateway:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;auth-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp/auth:latest&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Optional services — start only with a profile&lt;/span&gt;
  &lt;span class="na"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;./prometheus.yml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;/etc/prometheus/prometheus.yml&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Integration test dependencies&lt;/span&gt;
  &lt;span class="na"&gt;test-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;profiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;testing&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;testdb&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;testpass&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start core services only&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Start with monitoring stack&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; monitoring up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Start with testing dependencies&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; testing up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Start everything&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; monitoring &lt;span class="nt"&gt;--profile&lt;/span&gt; testing up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 3: Dependency Management with Health Checks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;myapp"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;start_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;    &lt;span class="c1"&gt;# Wait for db health check to pass&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;    &lt;span class="c1"&gt;# Wait for redis health check to pass&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://user:pass@db:5432/myapp&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis://redis:6379&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Plain &lt;code&gt;depends_on&lt;/code&gt; only waits for the container to start — not for the service inside to be ready. Always combine with &lt;code&gt;condition: service_healthy&lt;/code&gt; and a proper &lt;code&gt;healthcheck&lt;/code&gt; block to avoid startup race conditions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Docker Tools Ecosystem
&lt;/h2&gt;

&lt;p&gt;Essential tools that every Docker practitioner should know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Portainer — Web GUI
&lt;/h3&gt;

&lt;p&gt;Web-based interface for managing Docker containers, images, networks, and volumes. Ideal for teams and for environments where a GUI is preferred.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker volume create portainer_data
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 9000:9000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; portainer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; always &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; portainer_data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  portainer/portainer-ce:latest
&lt;span class="c"&gt;# Visit: http://localhost:9000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lazydocker — Terminal UI
&lt;/h3&gt;

&lt;p&gt;Terminal UI for Docker. View containers, logs, stats, and images interactively without typing commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_via_bash.sh | bash
lazydocker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  dive — Image Layer Analyzer
&lt;/h3&gt;

&lt;p&gt;Inspect Docker image layers interactively. Identify which files are added at each layer and find what is bloating image size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ubuntu&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;dive
&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;dive
&lt;span class="c"&gt;# Analyze any image&lt;/span&gt;
dive myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  hadolint — Dockerfile Linter
&lt;/h3&gt;

&lt;p&gt;Lint your Dockerfiles for best practice violations, security issues, and layer ordering problems.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run via Docker (no installation required)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; hadolint/hadolint &amp;lt; Dockerfile

&lt;span class="c"&gt;# Install locally on macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;hadolint

hadolint Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trivy — Security Scanner
&lt;/h3&gt;

&lt;p&gt;Comprehensive vulnerability scanner for container images. Detects CVEs in OS packages, language dependencies, and misconfigurations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh

&lt;span class="c"&gt;# Scan image for HIGH and CRITICAL CVEs only&lt;/span&gt;
trivy image &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH,CRITICAL myapp:latest

&lt;span class="c"&gt;# Fail CI on CRITICAL findings&lt;/span&gt;
trivy image &lt;span class="nt"&gt;--exit-code&lt;/span&gt; 1 &lt;span class="nt"&gt;--severity&lt;/span&gt; CRITICAL myapp:latest

&lt;span class="c"&gt;# Scan a Dockerfile for misconfigurations&lt;/span&gt;
trivy config Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Watchtower — Automatic Container Updates
&lt;/h3&gt;

&lt;p&gt;Automatically updates running containers when new images are pushed to the registry. Recommended for development and staging environments only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; watchtower &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock &lt;span class="se"&gt;\&lt;/span&gt;
  containrrr/watchtower &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interval&lt;/span&gt; 86400   &lt;span class="c"&gt;# Check daily&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ctop — Container Resource Monitor
&lt;/h3&gt;

&lt;p&gt;Real-time CPU, memory, network, and I/O metrics in a clean terminal interface — like &lt;code&gt;htop&lt;/code&gt; for Docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;ctop

&lt;span class="c"&gt;# Any OS via Docker&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; ctop &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock:ro &lt;span class="se"&gt;\&lt;/span&gt;
  quay.io/vektorlab/ctop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tools Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Free?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Portainer&lt;/td&gt;
&lt;td&gt;Web GUI&lt;/td&gt;
&lt;td&gt;Team management, beginners&lt;/td&gt;
&lt;td&gt;Yes (CE)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lazydocker&lt;/td&gt;
&lt;td&gt;Terminal UI&lt;/td&gt;
&lt;td&gt;Developers, quick inspection&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dive&lt;/td&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;Image size optimization&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hadolint&lt;/td&gt;
&lt;td&gt;Linter&lt;/td&gt;
&lt;td&gt;Dockerfile quality in CI&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;trivy&lt;/td&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;CVE scanning, compliance&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;watchtower&lt;/td&gt;
&lt;td&gt;Daemon&lt;/td&gt;
&lt;td&gt;Auto-updates (dev/staging)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ctop&lt;/td&gt;
&lt;td&gt;TUI&lt;/td&gt;
&lt;td&gt;Real-time monitoring&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Desktop&lt;/td&gt;
&lt;td&gt;GUI&lt;/td&gt;
&lt;td&gt;Mac/Windows developers&lt;/td&gt;
&lt;td&gt;Freemium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS Code Extension&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Development workflow&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Recommended starter stack — install &lt;strong&gt;Lazydocker&lt;/strong&gt; for daily use, &lt;strong&gt;dive&lt;/strong&gt; to check image size before every push, and integrate &lt;strong&gt;hadolint&lt;/strong&gt; and &lt;strong&gt;trivy&lt;/strong&gt; into your CI/CD pipeline. These four tools will catch most common issues automatically.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Further Learning Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Top Learning Resources
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;Why It Is Valuable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docker Official Docs&lt;/td&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docs.docker.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authoritative source, updated with every release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Play with Docker&lt;/td&gt;
&lt;td&gt;Interactive Lab&lt;/td&gt;
&lt;td&gt;&lt;code&gt;labs.play-with-docker.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Free browser-based Docker playground — no installation needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TechWorld with Nana&lt;/td&gt;
&lt;td&gt;YouTube&lt;/td&gt;
&lt;td&gt;&lt;code&gt;youtube.com/@TechWorldwithNana&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Best free Docker course on YouTube, extremely clear explanations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bret Fisher&lt;/td&gt;
&lt;td&gt;YouTube / Course&lt;/td&gt;
&lt;td&gt;&lt;code&gt;youtube.com/@BretFisher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deep DevOps focus, Docker Swarm, real-world production tips&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KodeKloud&lt;/td&gt;
&lt;td&gt;Course Platform&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kodekloud.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hands-on browser labs, great for beginners, Kubernetes learning path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Deep Dive (Nigel Poulton)&lt;/td&gt;
&lt;td&gt;Book&lt;/td&gt;
&lt;td&gt;Amazon / Leanpub&lt;/td&gt;
&lt;td&gt;Best Docker book — concise, practical, updated regularly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;awesome-docker&lt;/td&gt;
&lt;td&gt;GitHub List&lt;/td&gt;
&lt;td&gt;&lt;code&gt;github.com/veggiemonk/awesome-docker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Curated list of Docker tools, tutorials, and resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Official Blog&lt;/td&gt;
&lt;td&gt;Blog&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker.com/blog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Latest Docker updates, best practices, case studies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ivan Velichko's Blog&lt;/td&gt;
&lt;td&gt;Blog&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iximiuz.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deep internals: containers, namespaces, cgroups explained with clarity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4-Week Practice Plan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Week 1: Install Docker, run official images (nginx, postgres, redis, ubuntu)
        → docker run, docker ps, docker logs, docker exec
        → Understand port mapping, volume mounting, environment variables

Week 2: Write Dockerfiles for your existing React/Node projects
        → .dockerignore, layer caching, multi-stage builds
        → Optimize: get below 200 MB for your application image

Week 3: Docker Compose — build a full MERN/PERN stack locally
        → depends_on, healthchecks, named volumes
        → Development vs production compose files, profiles

Week 4: Security, multi-stage builds, push to ECR, deploy to ECS/Fargate
        → Non-root USER, trivy scan, hadolint check
        → GitHub Actions pipeline: build → push → deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Certification Paths
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Docker Certified Associate (DCA)&lt;/strong&gt;&lt;br&gt;
Official Docker certification covering installation, configuration, networking, security, and orchestration. A solid credential for validating foundational knowledge.&lt;br&gt;
Format: 55 MCQ, 90 minutes | Cost: approximately $195 USD&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CKA — Certified Kubernetes Administrator&lt;/strong&gt;&lt;br&gt;
The natural next step after mastering Docker. Kubernetes is Docker at scale, and your Docker knowledge maps directly to Kubernetes concepts.&lt;br&gt;
Format: Hands-on practical exam, 2 hours | Cost: approximately $395 USD&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Solutions Architect&lt;/strong&gt;&lt;br&gt;
Includes deep coverage of ECS, Fargate, ECR, and EKS. Highly valued in the job market for senior DevOps and cloud engineering roles.&lt;br&gt;
Format: 65 MCQ, 130 minutes | Cost: approximately $150 USD&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next? Your Docker Mastery Roadmap
&lt;/h2&gt;

&lt;p&gt;You now have the knowledge — the next step is execution. Here is a precise 30-day plan to go from Docker learner to production-ready DevOps engineer.&lt;/p&gt;

&lt;h3&gt;
  
  
  30-Day Docker Mastery Plan
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Days&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Task / Goal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;td&gt;Installation and First Steps&lt;/td&gt;
&lt;td&gt;Install Docker on Ubuntu, run hello-world, nginx, postgres — understand what each does&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3–5&lt;/td&gt;
&lt;td&gt;Container Commands&lt;/td&gt;
&lt;td&gt;Practice all docker run flags, docker logs, exec, inspect, stats — build muscle memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6–8&lt;/td&gt;
&lt;td&gt;Images Deep Dive&lt;/td&gt;
&lt;td&gt;Pull images, run docker history, inspect layers with dive, understand image size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9–12&lt;/td&gt;
&lt;td&gt;Dockerfile Mastery&lt;/td&gt;
&lt;td&gt;Write Dockerfiles for React, Node.js, and Python apps from scratch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13–15&lt;/td&gt;
&lt;td&gt;Multi-Stage Builds&lt;/td&gt;
&lt;td&gt;Optimize your apps: push image size from 1 GB down to 100 MB or less&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16–18&lt;/td&gt;
&lt;td&gt;Networking&lt;/td&gt;
&lt;td&gt;Create custom networks, wire up MERN stack containers, test DNS resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19–21&lt;/td&gt;
&lt;td&gt;Volumes and Persistence&lt;/td&gt;
&lt;td&gt;Database persistence across restarts, dev bind mounts, tmpfs for secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22–25&lt;/td&gt;
&lt;td&gt;Docker Compose&lt;/td&gt;
&lt;td&gt;Full stack with Compose: health checks, depends_on, dev/prod override files, profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26–27&lt;/td&gt;
&lt;td&gt;Security Hardening&lt;/td&gt;
&lt;td&gt;Non-root USER, trivy scan (zero HIGH/CRITICAL), hadolint (zero warnings), secrets management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28–29&lt;/td&gt;
&lt;td&gt;CI/CD Pipeline&lt;/td&gt;
&lt;td&gt;GitHub Actions: auto-build on push → push to Docker Hub or ECR → deploy to server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;AWS Deployment&lt;/td&gt;
&lt;td&gt;Push to ECR, create ECS task definition, deploy to Fargate — live in production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Docker to Kubernetes Transition Map
&lt;/h3&gt;

&lt;p&gt;Every Docker concept has a direct Kubernetes equivalent. Your Docker knowledge is your Kubernetes head start.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Docker Concept&lt;/th&gt;
&lt;th&gt;Kubernetes Equivalent&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Container&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Pod&lt;/code&gt; (wraps one or more containers)&lt;/td&gt;
&lt;td&gt;Pods are the smallest deployable unit in Kubernetes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kubectl run&lt;/code&gt; / Deployment&lt;/td&gt;
&lt;td&gt;Deployments add replicas, rollouts, rollbacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deployment + Service YAML&lt;/td&gt;
&lt;td&gt;Compose maps to multiple Kubernetes manifest files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Docker network&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Service + NetworkPolicy&lt;/td&gt;
&lt;td&gt;Kubernetes Services provide stable DNS endpoints for pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Docker volume&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PersistentVolume (PV/PVC)&lt;/td&gt;
&lt;td&gt;PVCs allow dynamic storage provisioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker compose up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kubectl apply -f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declarative — Kubernetes reconciles desired state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Docker Swarm service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deployment with replicas&lt;/td&gt;
&lt;td&gt;Kubernetes has much more sophisticated scheduling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HEALTHCHECK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;readinessProbe + livenessProbe&lt;/td&gt;
&lt;td&gt;Kubernetes has separate readiness, liveness, and startup probes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docker secret&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes Secret&lt;/td&gt;
&lt;td&gt;Kubernetes Secrets can be mounted as files or environment variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--env-file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ConfigMap + Secret&lt;/td&gt;
&lt;td&gt;ConfigMaps for non-sensitive config, Secrets for sensitive values&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Need to plan resource requests for Kubernetes? Use the &lt;a href="https://opscanopy.com/kubernetes-resource-calculator/" rel="noopener noreferrer"&gt;Kubernetes Resource Calculator&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Portfolio Projects for Senior DevOps Roles
&lt;/h3&gt;

&lt;p&gt;Build these five projects to make your GitHub profile stand out to technical recruiters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dockerized MERN Stack&lt;/strong&gt; — Full MERN (MongoDB, Express, React, Node) stack with Docker Compose. Proper health checks, named volumes, multi-stage build for React. Include a detailed README with an architecture diagram.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CI/CD Pipeline&lt;/strong&gt; — GitHub Actions workflow that automatically builds an image on push to main, runs tests, pushes to ECR with a version tag, and deploys to ECS/Fargate. Include a rollback workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Microservices Application&lt;/strong&gt; — Three or more services (auth, products, orders) each in separate containers with an API gateway. Service discovery via Docker DNS, isolated databases, event-driven communication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security-Hardened Image&lt;/strong&gt; — Dockerfile that passes a trivy scan with zero HIGH/CRITICAL CVEs, passes hadolint with zero warnings, runs as a non-root user, and uses multi-stage builds. Include scan reports in the README.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitoring Stack&lt;/strong&gt; — Application with Prometheus, Grafana, and Alertmanager in Docker Compose. Custom dashboards showing container metrics, application metrics, and alerting rules.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Moving Forward
&lt;/h3&gt;

&lt;p&gt;Completing this guide means you can confidently write production-grade Dockerfiles, deploy multi-container applications with Compose, use AWS ECR and ECS with Fargate, follow Docker security best practices, and tackle DevOps interviews with confidence.&lt;/p&gt;

&lt;p&gt;Your Docker knowledge is the foundation for everything in modern DevOps. The concepts of containers, immutable images, service isolation, and declarative configuration carry directly into the Kubernetes ecosystem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Prefer a day-by-day path? This is covered in &lt;a href="https://opscanopy.com/mission-90/" rel="noopener noreferrer"&gt;&lt;strong&gt;Mission 90 Days 22–30&lt;/strong&gt;&lt;/a&gt; — a free 90-day guided DevOps program with browser terminal missions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ready to orchestrate at scale? Continue with &lt;a href="https://opscanopy.com/learn/guides/kubernetes-for-devops/" rel="noopener noreferrer"&gt;Kubernetes for DevOps&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
      <category>dockerfile</category>
    </item>
    <item>
      <title>AWS for DevOps Engineers: The Core Services You Actually Need</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Sat, 15 Aug 2026 13:45:34 +0000</pubDate>
      <link>https://dev.to/opscanopy/aws-for-devops-engineers-the-core-services-you-actually-need-59a5</link>
      <guid>https://dev.to/opscanopy/aws-for-devops-engineers-the-core-services-you-actually-need-59a5</guid>
      <description>&lt;p&gt;A focused, interview-ready guide built for a developer moving into DevOps. Depth on the services that actually show up in DevOps work and interviews — not all 200+ AWS offerings. This guide covers Regions and AZs, IAM, EC2, Lambda, containers, VPC networking, S3/EBS/EFS storage, RDS and DynamoDB, the AWS CLI, and cost and security guardrails. See the &lt;a href="https://opscanopy.com/learn/roadmaps/aws/" rel="noopener noreferrer"&gt;AWS roadmap&lt;/a&gt; for a structured learning path through certifications and deeper specialisations.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Fundamentals
&lt;/h2&gt;

&lt;p&gt;Before you touch a single EC2 instance, you need the mental map: &lt;strong&gt;where&lt;/strong&gt; your stuff runs (Regions &amp;amp; AZs), &lt;strong&gt;who is responsible&lt;/strong&gt; for what (Shared Responsibility), and &lt;strong&gt;how you pay&lt;/strong&gt; for it (Billing &amp;amp; Free Tier). Get these three right and you avoid 90% of the surprises beginners hit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regions and Availability Zones
&lt;/h3&gt;

&lt;p&gt;AWS physically runs in &lt;strong&gt;Regions&lt;/strong&gt; (e.g. &lt;code&gt;us-east-1&lt;/code&gt; = N. Virginia, &lt;code&gt;eu-west-1&lt;/code&gt; = Ireland, &lt;code&gt;ap-southeast-1&lt;/code&gt; = Singapore). Each Region is a cluster of isolated datacenters grouped into &lt;strong&gt;Availability Zones (AZs)&lt;/strong&gt;. An AZ is one or more physical datacenters with independent power, cooling, and networking, but AZs in the same Region are linked by low-latency private fiber.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Region&lt;/strong&gt; = a city (N. Virginia). A geographic area you pick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AZ&lt;/strong&gt; = separate neighborhoods in that city (&lt;code&gt;us-east-1a&lt;/code&gt;, &lt;code&gt;us-east-1b&lt;/code&gt;, &lt;code&gt;us-east-1c&lt;/code&gt;), each with its own power grid, far enough apart that one flood/fire won't take down another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge Location&lt;/strong&gt; = a corner shop close to your house — used by CloudFront (CDN) and Route 53 to cache content near users for speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Think of a Region like a big housing society in one city. Each AZ is a separate building (Tower A, B, C) with its &lt;em&gt;own&lt;/em&gt; generator and water tank. If Tower A's power trips, Tower B is fine. So you spread your app across multiple towers — that's &lt;strong&gt;Multi-AZ&lt;/strong&gt;. Edge Locations are the ATMs scattered all over the city so you don't have to drive to the main bank for cash.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Why Multi-AZ Matters for High Availability
&lt;/h4&gt;

&lt;p&gt;If you run everything in one AZ and that AZ goes down, your app goes down. Period. Running across 2+ AZs means a failure in one is automatically absorbed by the others.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ RDS&lt;/strong&gt;: a standby DB copy sits in another AZ; on failure AWS auto-promotes it (failover in ~60–120s).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 + ELB Auto Scaling&lt;/strong&gt;: spread instances across AZs; the load balancer routes around the dead AZ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Region&lt;/strong&gt; is the next level up — for disaster recovery (DR) and global latency, not just AZ failure. It costs more and is harder (data replication, DNS failover).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A fintech runs its API on EC2 across &lt;code&gt;us-east-1a&lt;/code&gt; and &lt;code&gt;us-east-1b&lt;/code&gt; behind an Application Load Balancer, with RDS in Multi-AZ mode. When AWS had an AZ-level network issue, the ALB stopped sending traffic to the unhealthy AZ and RDS failed over to the standby. End users saw a 90-second blip instead of an outage. Single-AZ would have meant hours of downtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  How to Choose a Region
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pick the Region closest to your &lt;em&gt;users&lt;/em&gt;. Users in Southeast Asia → &lt;code&gt;ap-southeast-1&lt;/code&gt;. Test with cloudping or RTT measurements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prices differ per Region. &lt;code&gt;us-east-1&lt;/code&gt; is usually cheapest; some Regions (e.g. Sydney, São Paulo) can be pricier. Same instance, different bill.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance / Data residency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Customer/financial data may legally need to stay in-country → pick the matching Region. GDPR data → an EU Region like &lt;code&gt;eu-west-1&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service availability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Newest services launch in &lt;code&gt;us-east-1&lt;/code&gt; first. Some Regions lag. Check before committing.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Some services are &lt;strong&gt;global&lt;/strong&gt;, not regional: IAM, Route 53, CloudFront, and WAF (for CloudFront). But the AWS console quietly defaults a lot of regional resources to &lt;code&gt;us-east-1&lt;/code&gt; — always confirm your selected Region (top-right of the console) before creating resources, or you'll "lose" an EC2 instance that's actually running (and billing) in the wrong Region.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Region vs AZ vs Edge Location
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Availability Zone&lt;/th&gt;
&lt;th&gt;Edge Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it is&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Geographic area (a city)&lt;/td&gt;
&lt;td&gt;1+ isolated datacenters in a Region&lt;/td&gt;
&lt;td&gt;Small CDN/cache PoP near users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30+ globally&lt;/td&gt;
&lt;td&gt;Usually 3+ per Region&lt;/td&gt;
&lt;td&gt;400+ globally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Used for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Choosing where to deploy&lt;/td&gt;
&lt;td&gt;HA &amp;amp; fault tolerance within a Region&lt;/td&gt;
&lt;td&gt;Caching content close to users (low latency)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Example service&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Picked when launching EC2/RDS&lt;/td&gt;
&lt;td&gt;Multi-AZ RDS, Auto Scaling groups&lt;/td&gt;
&lt;td&gt;CloudFront, Route 53, Global Accelerator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Isolation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fully isolated from other Regions&lt;/td&gt;
&lt;td&gt;Independent power/cooling/network&lt;/td&gt;
&lt;td&gt;Not for compute — caching only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Classic question: "What's the difference between Multi-AZ and Multi-Region?" Answer crisply: &lt;strong&gt;Multi-AZ = High Availability&lt;/strong&gt; within one Region (handles datacenter/AZ failure, automatic, low cost). &lt;strong&gt;Multi-Region = Disaster Recovery + global reach&lt;/strong&gt; across Regions (handles a whole-Region failure, more complex, costlier, needs data replication and DNS failover). Don't conflate them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Shared Responsibility Model
&lt;/h3&gt;

&lt;p&gt;AWS does NOT secure everything for you. Security and compliance are &lt;strong&gt;shared&lt;/strong&gt;. The line is simple to remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS is responsible for the security OF the cloud&lt;/strong&gt; — the hardware, the physical datacenters, the host hypervisor, networking backbone, and the managed-service infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are responsible for security IN the cloud&lt;/strong&gt; — your data, your OS patches (on EC2), your IAM users/roles, your security groups, your application code, encryption settings.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; AWS is like a rental apartment building. The landlord (AWS) handles the building structure, the main gate security, the foundation, electricity supply. But &lt;em&gt;inside your flat&lt;/em&gt; — locking your own door, what you keep inside, who you give a duplicate key to (IAM keys!) — that's on you. If you leave your flat door wide open, you can't blame the landlord.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWS — "OF the cloud"&lt;/th&gt;
&lt;th&gt;You (DevOps) — "IN the cloud"&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Physical datacenter &amp;amp; hardware security&lt;/td&gt;
&lt;td&gt;IAM users, roles, groups, MFA, least privilege&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hypervisor &amp;amp; host OS patching&lt;/td&gt;
&lt;td&gt;Guest OS patching on EC2 (you own the AMI/instance)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global network infrastructure&lt;/td&gt;
&lt;td&gt;Security Groups, NACLs, VPC subnet design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed service uptime (S3, RDS engine, Lambda runtime)&lt;/td&gt;
&lt;td&gt;Your data, client/server-side encryption, S3 bucket policies (don't make it public by accident!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decommissioning/wiping failed disks&lt;/td&gt;
&lt;td&gt;Your application code &amp;amp; its vulnerabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; The responsibility line &lt;em&gt;shifts&lt;/em&gt; with the service type. On &lt;strong&gt;EC2 (IaaS)&lt;/strong&gt; you patch the OS. On &lt;strong&gt;RDS (PaaS)&lt;/strong&gt; AWS patches the DB engine but you still manage users, schemas, and encryption. On &lt;strong&gt;Lambda/S3 (serverless/managed)&lt;/strong&gt; AWS handles even more, but data security and access policies are &lt;em&gt;always&lt;/em&gt; yours. The most common breach in the news? A publicly-exposed S3 bucket — 100% the customer's fault, never AWS's.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Billing and Free Tier
&lt;/h3&gt;

&lt;p&gt;AWS pricing is fundamentally &lt;strong&gt;pay-as-you-go&lt;/strong&gt;: no upfront cost (unless you choose to commit), you pay only for what you actually use — per second/hour of compute, per GB of storage, per GB of data transfer out. No usage = (mostly) no bill. But "mostly" is where beginners get bitten.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Pay-as-you-go is like an electricity meter, not a fixed monthly rent. You only pay for the units you burn. But some things keep the meter running even when you're "not using" them — like a reserved parking spot you booked but left empty (an idle Elastic IP), or a security guard you hired and forgot to send home (a running NAT Gateway). The meter doesn't care that you forgot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  The 3 Types of Free Tier
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;How long&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Always Free&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Forever (within limits)&lt;/td&gt;
&lt;td&gt;Lambda 1M requests/month, DynamoDB 25 GB, CloudWatch basic metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;12-Month Free&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12 months from signup&lt;/td&gt;
&lt;td&gt;EC2 &lt;code&gt;t2.micro&lt;/code&gt;/&lt;code&gt;t3.micro&lt;/code&gt; 750 hrs/mo, S3 5 GB, RDS 750 hrs/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trials&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Short fixed window (e.g. 30/90 days)&lt;/td&gt;
&lt;td&gt;Short-term service trials like certain SageMaker/Inspector features&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; What surprises beginners and quietly inflates the bill — even on "free tier": &lt;strong&gt;1) Data transfer OUT:&lt;/strong&gt; data going INTO AWS is free, but data going OUT to the internet costs money beyond the small free allowance. &lt;strong&gt;2) Idle Elastic IPs (EIPs):&lt;/strong&gt; an EIP is free &lt;em&gt;only while attached to a running instance&lt;/em&gt;. Allocate one and leave it unattached and you pay hourly. &lt;strong&gt;3) NAT Gateway:&lt;/strong&gt; charges both an hourly rate AND per-GB processed — and it is NOT free tier. Spin one up in a tutorial, forget it, and it bleeds money 24/7. This is the #1 "why is my bill so high?" culprit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; On day one: set a &lt;strong&gt;Billing Alarm&lt;/strong&gt; in CloudWatch (or AWS Budgets) for a low threshold like $5–$10. Enable &lt;strong&gt;Cost Explorer&lt;/strong&gt; and check it weekly. Also stop/terminate tutorial resources when done — &lt;code&gt;stop&lt;/code&gt; halts EC2 compute charges but you still pay for the EBS volume; &lt;code&gt;terminate&lt;/code&gt; kills it fully. Tag resources by project so you can see exactly what's costing what.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  IAM — Identity and Access Management
&lt;/h2&gt;

&lt;p&gt;IAM is the &lt;strong&gt;front gate and key system&lt;/strong&gt; of your entire AWS account: who can log in, and what they're allowed to do. It's the #1 DevOps interview area because almost every security incident traces back to bad IAM. IAM is &lt;strong&gt;global&lt;/strong&gt; (not regional) and, importantly, &lt;strong&gt;free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
An IAM user or an EC2 instance role both attach to a policy, which defines which AWS resources and actions are permitted.





&lt;h3&gt;
  
  
  Root User vs IAM User
&lt;/h3&gt;

&lt;p&gt;When you create an AWS account, you get a &lt;strong&gt;root user&lt;/strong&gt; (the email you signed up with). The root user has unlimited, unrestricted power — it can do &lt;em&gt;anything&lt;/em&gt;, including close the account and change billing. That's exactly why you should almost never use it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Root user&lt;/strong&gt;: full god-mode access. Use it only for a tiny set of tasks that &lt;em&gt;require&lt;/em&gt; root (e.g. changing account settings, closing the account).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM user&lt;/strong&gt;: an identity you create for a person or app, with &lt;em&gt;only&lt;/em&gt; the permissions they need.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The root user is the &lt;strong&gt;master key&lt;/strong&gt; to the whole building — opens every door, the safe, the main switchboard, everything. You don't carry the master key around daily; you lock it in a vault, put a heavy alarm (MFA) on it, and hand out individual room keys (IAM users) for day-to-day work. If you lose the master key, the whole building is compromised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Lock away root immediately: (1) enable &lt;strong&gt;MFA on root&lt;/strong&gt;, (2) delete any root &lt;strong&gt;access keys&lt;/strong&gt; — root should never have programmatic keys, (3) create an admin IAM user (or, better, use IAM Identity Center / SSO) for daily work, (4) never share or hardcode root credentials. A leaked root key = total account takeover, including running up massive bills and deleting all your data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Users vs Groups
&lt;/h3&gt;

&lt;p&gt;Attaching policies to each user individually doesn't scale. &lt;strong&gt;Groups&lt;/strong&gt; are collections of users — you attach policies to the group, and every member inherits them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User&lt;/strong&gt;: a single identity (a person or a service account).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group&lt;/strong&gt;: e.g. &lt;code&gt;Developers&lt;/code&gt;, &lt;code&gt;Admins&lt;/code&gt;, &lt;code&gt;ReadOnly&lt;/code&gt;. Add a user to the group and they get its permissions. Remove them and access goes away instantly.&lt;/li&gt;
&lt;li&gt;A group is NOT an identity — you can't make a group the "principal" of an action, and groups can't be nested.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Policies — The Rules of Access
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;policy&lt;/strong&gt; is a JSON document defining permissions. Two big ways to categorize them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Identity-based policy&lt;/th&gt;
&lt;th&gt;Resource-based policy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Attached to&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An IAM user, group, or role&lt;/td&gt;
&lt;td&gt;A resource (e.g. S3 bucket, SQS queue, KMS key)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Answers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"What can this identity do?"&lt;/td&gt;
&lt;td&gt;"Who can access this resource?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Has a Principal?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (the identity is the principal)&lt;/td&gt;
&lt;td&gt;Yes — specifies &lt;em&gt;who&lt;/em&gt; (account/user/service)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-account&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not directly&lt;/td&gt;
&lt;td&gt;Yes — great for sharing across accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Devs can read S3 bucket X"&lt;/td&gt;
&lt;td&gt;"S3 bucket policy: allow account B to read"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Managed vs inline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Managed&lt;/strong&gt;: prebuilt by AWS (e.g. &lt;code&gt;AmazonS3ReadOnlyAccess&lt;/code&gt;). Convenient, auto-updated, but often broader than you need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer Managed&lt;/strong&gt;: you create and reuse them across identities. Best for least privilege — version-controlled, reusable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline&lt;/strong&gt;: embedded directly into one user/group/role, 1:1, deleted with it. Use sparingly — for strict one-off relationships only.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Policy JSON Structure
&lt;/h4&gt;

&lt;p&gt;Every statement has an &lt;strong&gt;Effect&lt;/strong&gt; (Allow/Deny), &lt;strong&gt;Action&lt;/strong&gt; (the API calls), &lt;strong&gt;Resource&lt;/strong&gt; (the ARN it applies to), and optionally a &lt;strong&gt;Condition&lt;/strong&gt;. This one lets a user list a specific bucket and read/write objects only under the &lt;code&gt;app/&lt;/code&gt; prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Version"&lt;/span&gt;: &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;,
  &lt;span class="s2"&gt;"Statement"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"Sid"&lt;/span&gt;: &lt;span class="s2"&gt;"ListTheBucket"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:s3:::my-app-bucket"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Condition"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"StringLike"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"s3:prefix"&lt;/span&gt;: &lt;span class="s2"&gt;"app/*"&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"Sid"&lt;/span&gt;: &lt;span class="s2"&gt;"ReadWriteAppObjects"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Effect"&lt;/span&gt;: &lt;span class="s2"&gt;"Allow"&lt;/span&gt;,
      &lt;span class="s2"&gt;"Action"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;,
        &lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;,
      &lt;span class="s2"&gt;"Resource"&lt;/span&gt;: &lt;span class="s2"&gt;"arn:aws:s3:::my-app-bucket/app/*"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Remember the evaluation logic: &lt;strong&gt;everything is implicitly denied by default&lt;/strong&gt;. An explicit &lt;code&gt;Allow&lt;/code&gt; grants access, but an explicit &lt;code&gt;Deny&lt;/code&gt; &lt;em&gt;always wins&lt;/em&gt; over any Allow. So you can broadly allow, then surgically deny sensitive actions. Interviewers love asking "Allow vs Deny — which wins?" Answer: &lt;strong&gt;explicit Deny always trumps Allow.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  IAM Roles — Why Roles Beat Long-Lived Keys
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;role&lt;/strong&gt; is an identity with permissions but &lt;strong&gt;no permanent credentials&lt;/strong&gt;. Instead, a trusted entity &lt;em&gt;assumes&lt;/em&gt; the role and gets &lt;strong&gt;temporary credentials&lt;/strong&gt; (via STS) that auto-expire. This is the single most important IAM best practice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; An access key is like a permanent house key you cut and hand out — if it leaks, it works forever until someone notices and changes the lock. A role is like a &lt;strong&gt;hotel keycard&lt;/strong&gt;: the front desk (STS) issues a fresh card that stops working after checkout. Even if someone copies it, it's useless in a few hours. This is why roles always beat long-lived keys.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Where roles shine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2 Instance Profile&lt;/strong&gt;: attach a role to an EC2 instance so your app calls AWS APIs without ever storing keys on the box. The SDK auto-fetches rotating temp creds from instance metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AssumeRole&lt;/strong&gt;: a user/service calls &lt;code&gt;sts:AssumeRole&lt;/code&gt; to temporarily become the role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-account access&lt;/strong&gt;: account A's role trusts account B; B's users assume it — no shared keys between accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service roles&lt;/strong&gt;: e.g. Lambda execution role, ECS task role — the AWS service assumes the role to act on your behalf.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Never hardcode access keys in code, Dockerfiles, environment files committed to Git, or AMIs. Leaked keys on public GitHub are scraped by bots within &lt;em&gt;minutes&lt;/em&gt; and used to spin up crypto-mining fleets — landing you a five-figure bill. On EC2/ECS/Lambda, always use a role instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  IAM User vs IAM Role
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;IAM User&lt;/th&gt;
&lt;th&gt;IAM Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Credentials&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long-lived (password / access keys)&lt;/td&gt;
&lt;td&gt;Temporary, auto-expiring (via STS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who uses it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A specific person/app, persistently&lt;/td&gt;
&lt;td&gt;Anyone/anything trusted who assumes it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human console login&lt;/td&gt;
&lt;td&gt;EC2/Lambda/ECS, cross-account, federation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rotation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual, easy to forget&lt;/td&gt;
&lt;td&gt;Automatic — handled by AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Leak risk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (keys live forever)&lt;/td&gt;
&lt;td&gt;Low (creds expire in minutes/hours)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  IAM CLI Examples
&lt;/h3&gt;

&lt;p&gt;Create a user, put them in a group, attach a managed policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create an IAM user&lt;/span&gt;
aws iam create-user &lt;span class="nt"&gt;--user-name&lt;/span&gt; riya-dev

&lt;span class="c"&gt;# Create a group and attach an AWS managed policy to it&lt;/span&gt;
aws iam create-group &lt;span class="nt"&gt;--group-name&lt;/span&gt; Developers
aws iam attach-group-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-name&lt;/span&gt; Developers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

&lt;span class="c"&gt;# Add the user to the group (inherits the group's permissions)&lt;/span&gt;
aws iam add-user-to-group &lt;span class="nt"&gt;--user-name&lt;/span&gt; riya-dev &lt;span class="nt"&gt;--group-name&lt;/span&gt; Developers

&lt;span class="c"&gt;# Enforce MFA-friendly console access (set a login profile / password)&lt;/span&gt;
aws iam create-login-profile &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-name&lt;/span&gt; riya-dev &lt;span class="nt"&gt;--password&lt;/span&gt; &lt;span class="s1"&gt;'Temp#Pass123'&lt;/span&gt; &lt;span class="nt"&gt;--password-reset-required&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;strong&gt;role&lt;/strong&gt; with a trust policy so EC2 can assume it, then give the role permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) Trust policy: WHO is allowed to assume this role (here: the EC2 service)&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; trust-policy.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "ec2.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# 2) Create the role with that trust policy&lt;/span&gt;
aws iam create-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; app-s3-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--assume-role-policy-document&lt;/span&gt; file://trust-policy.json

&lt;span class="c"&gt;# 3) Attach a permissions policy (WHAT the role can do)&lt;/span&gt;
aws iam attach-role-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; app-s3-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

&lt;span class="c"&gt;# 4) Wrap it in an instance profile and attach to EC2 (no keys needed!)&lt;/span&gt;
aws iam create-instance-profile &lt;span class="nt"&gt;--instance-profile-name&lt;/span&gt; app-s3-profile
aws iam add-role-to-instance-profile &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instance-profile-name&lt;/span&gt; app-s3-profile &lt;span class="nt"&gt;--role-name&lt;/span&gt; app-s3-role
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Two policy types per role: the &lt;strong&gt;trust policy&lt;/strong&gt; (the &lt;code&gt;assume-role-policy-document&lt;/code&gt;) controls &lt;em&gt;WHO can assume&lt;/em&gt; the role, and the &lt;strong&gt;permissions policy&lt;/strong&gt; controls &lt;em&gt;WHAT&lt;/em&gt; the role can do once assumed. Interviewers often probe this distinction — keep them separate in your head.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Least Privilege
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Grant only the minimum permissions needed to do the job — nothing more.&lt;/strong&gt; Start with zero, add permissions as required, and scope tightly (specific actions, specific resource ARNs, conditions).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer specific actions (&lt;code&gt;s3:GetObject&lt;/code&gt;) over wildcards (&lt;code&gt;s3:*&lt;/code&gt; or &lt;code&gt;*&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Scope &lt;code&gt;Resource&lt;/code&gt; to exact ARNs, not &lt;code&gt;"*"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;IAM Access Analyzer&lt;/strong&gt; to generate least-privilege policies from real CloudTrail usage.&lt;/li&gt;
&lt;li&gt;Review with the &lt;strong&gt;IAM Policy Simulator&lt;/strong&gt; before rollout. Audit with Access Analyzer / credential reports regularly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  MFA and Access Key Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MFA everywhere&lt;/strong&gt;: mandatory on root and all human users. Add a virtual/hardware MFA device. You can even &lt;em&gt;require&lt;/em&gt; MFA in policies via a &lt;code&gt;Condition&lt;/code&gt; (&lt;code&gt;aws:MultiFactorAuthPresent&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access keys&lt;/strong&gt;: only for programmatic CLI/SDK use where roles aren't possible. Rotate them regularly, never commit to Git, and delete unused keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer roles&lt;/strong&gt; over access keys whenever the workload runs inside AWS (EC2/ECS/Lambda) — zero keys to manage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt;: use IAM credential reports and CloudTrail to find stale keys and unused permissions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Compute — EC2, Lambda, Containers
&lt;/h2&gt;

&lt;p&gt;Compute is "where your code actually runs." On AWS you pick how much control you want versus how much AWS manages for you. More control = more responsibility (EC2). Less control = AWS handles the boring stuff (Lambda, Fargate). As a DevOps engineer your job is choosing the right one for the workload.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;
EC2 gives full OS control for long-running workloads; Lambda is event-driven and fully managed, scaling to zero between invocations.





&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Think of renting a place. &lt;strong&gt;EC2&lt;/strong&gt; is renting an empty flat — you bring furniture, fix the plumbing, control everything. &lt;strong&gt;Lambda&lt;/strong&gt; is a hotel room you only pay for the minutes you're inside. &lt;strong&gt;Fargate&lt;/strong&gt; is a serviced apartment — your stuff, but housekeeping (servers) is handled. Same "shelter," very different effort.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  EC2 — Elastic Compute Cloud
&lt;/h3&gt;

&lt;p&gt;EC2 is a virtual server (a VM) in AWS's data center. You choose the OS, CPU, RAM, disk, and you SSH in like any Linux box. It's the foundational "rent a server" service.&lt;/p&gt;

&lt;h4&gt;
  
  
  Instance Families and Types
&lt;/h4&gt;

&lt;p&gt;Instances are grouped into &lt;strong&gt;families&lt;/strong&gt; optimized for different workloads. The naming follows a pattern: &lt;code&gt;m5.large&lt;/code&gt; = family &lt;code&gt;m&lt;/code&gt;, generation &lt;code&gt;5&lt;/code&gt;, size &lt;code&gt;large&lt;/code&gt;. Bigger size = more vCPU + RAM (and roughly double the price each step up).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;General purpose (t, m)&lt;/strong&gt; — balanced CPU/RAM. &lt;code&gt;t3.micro&lt;/code&gt; (burstable, cheap, great for dev/small web), &lt;code&gt;m5.large&lt;/code&gt; (steady workloads, app servers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute optimized (c)&lt;/strong&gt; — high CPU per RAM. &lt;code&gt;c5.xlarge&lt;/code&gt; for batch processing, gaming servers, CI build runners.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory optimized (r, x)&lt;/strong&gt; — lots of RAM. &lt;code&gt;r5.large&lt;/code&gt; for databases, in-memory caches (Redis), big data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage optimized (i, d)&lt;/strong&gt; — high disk throughput/IOPS. &lt;code&gt;i3.large&lt;/code&gt; for NoSQL, data warehousing, large local datasets.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;t&lt;/code&gt;-family instances are &lt;em&gt;burstable&lt;/em&gt; — they earn CPU credits when idle and spend them during spikes. Perfect for spiky low-traffic apps, but a constantly-busy server will run out of credits and throttle. For steady load, use &lt;code&gt;m&lt;/code&gt;/&lt;code&gt;c&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  AMIs (Amazon Machine Images)
&lt;/h4&gt;

&lt;p&gt;An AMI is the template an instance boots from — OS + pre-installed software + config. Think of it as a snapshot/golden image. You launch from an AWS-provided AMI (Amazon Linux, Ubuntu) or bake your own with your app baked in (faster, repeatable deploys — a core DevOps practice with tools like Packer).&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Pairs (SSH)
&lt;/h4&gt;

&lt;p&gt;To log into a Linux EC2 instance you use SSH key-based auth — no passwords. AWS gives you the &lt;strong&gt;public&lt;/strong&gt; key (placed on the instance), you keep the &lt;strong&gt;private&lt;/strong&gt; &lt;code&gt;.pem&lt;/code&gt; file. Lose the private key and you're locked out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a key pair and save the private key locally&lt;/span&gt;
aws ec2 create-key-pair &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key-name&lt;/span&gt; my-devops-key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'KeyMaterial'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; my-devops-key.pem

&lt;span class="c"&gt;# Lock down permissions (SSH refuses world-readable keys)&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;400 my-devops-key.pem

&lt;span class="c"&gt;# Later, connect to the instance&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; my-devops-key.pem ec2-user@&amp;lt;PUBLIC_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  User Data (Bootstrap Script)
&lt;/h4&gt;

&lt;p&gt;User data is a script that runs &lt;strong&gt;once, on first boot&lt;/strong&gt;, as root. This is how you bootstrap a fresh instance — install packages, pull config, start your app — without manually SSHing in. Essential for automation and Auto Scaling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# user-data.sh — bootstrap a simple web server on first boot&lt;/span&gt;
yum update &lt;span class="nt"&gt;-y&lt;/span&gt;
yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nginx
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;h1&amp;gt;Deployed via user data&amp;lt;/h1&amp;gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /usr/share/nginx/html/index.html
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;nginx
systemctl start nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Launching an Instance via CLI
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Launch a t3.micro with the bootstrap script and our key&lt;/span&gt;
aws ec2 run-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-id&lt;/span&gt; ami-0abcdef1234567890 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instance-type&lt;/span&gt; t3.micro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key-name&lt;/span&gt; my-devops-key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-group-ids&lt;/span&gt; sg-0123456789abcdef0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet-id&lt;/span&gt; subnet-0123456789abcdef0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-data&lt;/span&gt; file://user-data.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag-specifications&lt;/span&gt; &lt;span class="s1"&gt;'ResourceType=instance,Tags=[{Key=Name,Value=web-01}]'&lt;/span&gt;

&lt;span class="c"&gt;# See what's running (handy filtered view)&lt;/span&gt;
aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="s2"&gt;"Name=instance-state-name,Values=running"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType,IP:PublicIpAddress}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pricing Models
&lt;/h4&gt;

&lt;p&gt;Same instance, very different bill depending on how you buy it. Interviewers love this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On-Demand&lt;/strong&gt; — pay per second, no commitment. Most expensive per hour, max flexibility. Use for unpredictable/short workloads and testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reserved Instances (RI)&lt;/strong&gt; — commit to a specific instance type for 1 or 3 years. Up to ~72% off. Use for steady, predictable baseline load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Savings Plans&lt;/strong&gt; — commit to a $/hour spend for 1–3 years; more flexible than RIs (applies across families/regions). The modern preferred way to save on steady load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot Instances&lt;/strong&gt; — bid on spare AWS capacity, up to ~90% off, but AWS can reclaim them with a 2-minute warning. Use for fault-tolerant, stateless, interruptible work (CI builds, batch jobs, big data).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A typical production setup mixes them: &lt;strong&gt;Reserved/Savings Plans&lt;/strong&gt; for the always-on baseline (web servers running 24/7), &lt;strong&gt;Spot&lt;/strong&gt; for the Auto Scaling group that handles traffic spikes and nightly batch jobs, and &lt;strong&gt;On-Demand&lt;/strong&gt; as fallback when Spot capacity isn't available. This can cut a compute bill by half or more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Lambda vs EC2 — Serverless or Server?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lambda&lt;/strong&gt; runs your code in response to events (an HTTP request via API Gateway, a file landing in S3, a message in a queue) with zero servers to manage. AWS provisions, scales, and bills only for execution time (per millisecond). You give it a function; it runs it. But it's short-lived (15-min max), stateless, and you don't control the OS.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;EC2&lt;/th&gt;
&lt;th&gt;Lambda&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Long-running virtual server&lt;/td&gt;
&lt;td&gt;Event-driven function (serverless)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You manage&lt;/td&gt;
&lt;td&gt;OS, patching, scaling, capacity&lt;/td&gt;
&lt;td&gt;Just your code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Manual / Auto Scaling groups&lt;/td&gt;
&lt;td&gt;Automatic, instant, per-request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing&lt;/td&gt;
&lt;td&gt;Per second while running (even idle)&lt;/td&gt;
&lt;td&gt;Per ms of execution; $0 when idle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max runtime&lt;/td&gt;
&lt;td&gt;Runs forever&lt;/td&gt;
&lt;td&gt;15 minutes per invocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Stateful (local disk, memory persist)&lt;/td&gt;
&lt;td&gt;Stateless (cold starts possible)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Steady traffic, full control, legacy apps, databases&lt;/td&gt;
&lt;td&gt;Spiky/event-driven work, APIs, glue code, cron jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; "When would you NOT use Lambda?" Strong answer: long-running jobs (&amp;gt;15 min), workloads needing predictable low latency (cold starts hurt), apps requiring specific OS/runtime control, or steady high-volume traffic where always-on EC2 is actually cheaper. Lambda shines for bursty, event-driven, "scale-to-zero" workloads.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Containers: ECS vs EKS vs Fargate
&lt;/h3&gt;

&lt;p&gt;First, separate two things: the &lt;strong&gt;orchestrator&lt;/strong&gt; (decides where containers run, restarts them, scales them) from the &lt;strong&gt;compute&lt;/strong&gt; (the actual machines they run on).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ECS&lt;/strong&gt; (Elastic Container Service) — AWS's own orchestrator. Simpler, deeply AWS-integrated, no K8s knowledge needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EKS&lt;/strong&gt; (Elastic Kubernetes Service) — managed Kubernetes. Industry-standard, portable across clouds, bigger learning curve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fargate&lt;/strong&gt; — NOT an orchestrator; it's a &lt;em&gt;serverless compute engine&lt;/em&gt;. It runs containers for ECS or EKS without you managing any EC2 nodes. "Containers without servers."&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ECS&lt;/th&gt;
&lt;th&gt;EKS&lt;/th&gt;
&lt;th&gt;Fargate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it is&lt;/td&gt;
&lt;td&gt;AWS-native orchestrator&lt;/td&gt;
&lt;td&gt;Managed Kubernetes orchestrator&lt;/td&gt;
&lt;td&gt;Serverless compute for containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs on&lt;/td&gt;
&lt;td&gt;EC2 or Fargate&lt;/td&gt;
&lt;td&gt;EC2 or Fargate&lt;/td&gt;
&lt;td&gt;N/A (it IS the compute)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Low (AWS-specific)&lt;/td&gt;
&lt;td&gt;High (full K8s)&lt;/td&gt;
&lt;td&gt;Low — no servers to manage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portability&lt;/td&gt;
&lt;td&gt;AWS-locked&lt;/td&gt;
&lt;td&gt;Portable (standard K8s)&lt;/td&gt;
&lt;td&gt;AWS-only billing model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You manage nodes?&lt;/td&gt;
&lt;td&gt;Yes (if EC2 mode)&lt;/td&gt;
&lt;td&gt;Yes (if EC2 mode)&lt;/td&gt;
&lt;td&gt;No — AWS handles it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Simple AWS-only container apps&lt;/td&gt;
&lt;td&gt;K8s-standard, multi-cloud, complex orchestration&lt;/td&gt;
&lt;td&gt;Anyone who wants containers without managing servers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; "ECS vs EKS" and "Fargate" are not three competitors — Fargate is a &lt;em&gt;launch type&lt;/em&gt; for ECS or EKS. A common combo is "ECS on Fargate": AWS-native simplicity + zero server management. Pick EKS only when you genuinely need Kubernetes (portability, team already knows K8s, complex workloads).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Networking — VPC and Friends
&lt;/h2&gt;

&lt;p&gt;Networking is where a lot of DevOps engineers get stuck — and where interviewers love to probe. The good news: you already know networking basics (IPs, CIDR, routing). AWS just gives you software-defined versions of the same physical concepts. For a deeper treatment of subnets, CIDR design, and routing, see &lt;a href="https://opscanopy.com/learn/guides/networking-for-devops/" rel="noopener noreferrer"&gt;Networking for DevOps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
A VPC with a public subnet (IGW + NAT Gateway) and a private subnet (EC2); public traffic enters via the IGW while EC2 reaches the internet outbound through the NAT Gateway.





&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A &lt;strong&gt;VPC&lt;/strong&gt; is your own private gated society (housing colony) inside AWS's huge city. &lt;strong&gt;Subnets&lt;/strong&gt; are blocks within the society. The &lt;strong&gt;route table&lt;/strong&gt; is the society's road map (which road leads where). The &lt;strong&gt;Internet Gateway&lt;/strong&gt; is the main gate to the outside world. &lt;strong&gt;Security Groups&lt;/strong&gt; are guards at each house's door; &lt;strong&gt;NACLs&lt;/strong&gt; are guards at the block's entrance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  VPC (Virtual Private Cloud)
&lt;/h3&gt;

&lt;p&gt;A VPC is your own isolated virtual network inside AWS. Nothing outside it can reach in unless you explicitly allow it. You define its IP range using a &lt;strong&gt;CIDR block&lt;/strong&gt;, e.g. &lt;code&gt;10.0.0.0/16&lt;/code&gt; — that gives you ~65,536 private IP addresses to carve up. Size your VPC subnets with the &lt;a href="https://opscanopy.com/subnet-calculator/" rel="noopener noreferrer"&gt;Subnet Calculator&lt;/a&gt; and validate CIDR ranges with the &lt;a href="https://opscanopy.com/cidr-checker/" rel="noopener noreferrer"&gt;CIDR Checker&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  CIDR Blocks — Quick Refresher
&lt;/h3&gt;

&lt;p&gt;CIDR notation = IP + "how many bits are fixed." &lt;code&gt;/16&lt;/code&gt; fixes the first 16 bits → &lt;code&gt;10.0.x.x&lt;/code&gt; is yours (65,536 IPs). &lt;code&gt;/24&lt;/code&gt; fixes 24 bits → &lt;code&gt;10.0.1.x&lt;/code&gt; (256 IPs). Smaller number after the slash = bigger network. You give the VPC a large block (&lt;code&gt;/16&lt;/code&gt;) and slice subnets out of it (&lt;code&gt;/24&lt;/code&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; AWS reserves &lt;strong&gt;5 IPs in every subnet&lt;/strong&gt; (network address, VPC router, DNS, future use, broadcast). So a &lt;code&gt;/24&lt;/code&gt; subnet gives you 256 − 5 = 251 usable IPs, not 256. This trips people up when sizing subnets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Subnets: Public vs Private
&lt;/h3&gt;

&lt;p&gt;A subnet is a slice of the VPC, and it lives in &lt;strong&gt;one Availability Zone&lt;/strong&gt;. The big question: &lt;em&gt;what actually makes a subnet "public"?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's NOT a checkbox called "public." A subnet is &lt;strong&gt;public&lt;/strong&gt; if and only if its &lt;strong&gt;route table has a route to an Internet Gateway&lt;/strong&gt; (&lt;code&gt;0.0.0.0/0 → igw-xxxx&lt;/code&gt;). A &lt;strong&gt;private&lt;/strong&gt; subnet has no such route — its traffic can't reach the internet directly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Classic question: "What makes a subnet public?" Wrong answer: "it has public IPs." Correct answer: &lt;strong&gt;its route table routes &lt;code&gt;0.0.0.0/0&lt;/code&gt; to an Internet Gateway.&lt;/strong&gt; An instance also needs a public IP to be reachable, but the defining factor is the route to the IGW.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Route Tables
&lt;/h3&gt;

&lt;p&gt;A route table is a set of rules deciding where network traffic goes. Each subnet is associated with exactly one route table. Every table has a &lt;strong&gt;local&lt;/strong&gt; route (so subnets within the VPC can talk to each other automatically). You add routes for outside traffic — to an IGW (public) or a NAT Gateway (private).&lt;/p&gt;

&lt;h3&gt;
  
  
  Internet Gateway (IGW)
&lt;/h3&gt;

&lt;p&gt;The IGW is a horizontally-scaled, highly-available component attached to your VPC that allows &lt;strong&gt;two-way&lt;/strong&gt; communication between the VPC and the internet. One IGW per VPC. Without it, nothing in your VPC can reach the public internet, period.&lt;/p&gt;

&lt;h3&gt;
  
  
  NAT Gateway — Why Private Subnets Need It
&lt;/h3&gt;

&lt;p&gt;Your app servers sit in a &lt;strong&gt;private&lt;/strong&gt; subnet (no IGW route — good, attackers can't reach them). But they still need to download OS updates, pull packages, call external APIs. How do they reach &lt;em&gt;out&lt;/em&gt; without being reachable &lt;em&gt;in&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Answer: a &lt;strong&gt;NAT Gateway&lt;/strong&gt;. It lives in a &lt;em&gt;public&lt;/em&gt; subnet and does Network Address Translation. Private instances route &lt;code&gt;0.0.0.0/0 → NAT Gateway&lt;/code&gt;; the NAT forwards their outbound requests to the internet using its own public IP, and returns the responses. Crucially, NAT only allows &lt;strong&gt;outbound-initiated&lt;/strong&gt; traffic — the outside world cannot start a connection back in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; NAT Gateway is like a hotel front desk. Guests (private servers) can call out for room service or order food from outside, but no outsider can directly walk to your room — they only reach the front desk. Calls go out, randos can't come in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; NAT Gateways are NOT free — you pay per hour &lt;em&gt;and&lt;/em&gt; per GB processed. They're a common surprise on AWS bills. Also, a NAT Gateway lives in one AZ; for high availability you deploy one per AZ. Don't put it in a private subnet by mistake (it needs the IGW route to work).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Request Flow: User to App
&lt;/h3&gt;

&lt;p&gt;Walking through a request to a web app makes it click:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User's browser sends a request to your app's public IP / load balancer.&lt;/li&gt;
&lt;li&gt;Traffic hits the &lt;strong&gt;Internet Gateway&lt;/strong&gt; (the VPC's front gate).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;public subnet's route table&lt;/strong&gt; directs it to the resource (e.g. a load balancer or web server with a public IP).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;NACL&lt;/strong&gt; (block-level guard) checks subnet rules, then the &lt;strong&gt;Security Group&lt;/strong&gt; (door-level guard) checks if the port is allowed.&lt;/li&gt;
&lt;li&gt;The web tier forwards to app servers in the &lt;strong&gt;private subnet&lt;/strong&gt; over the VPC's internal &lt;code&gt;local&lt;/code&gt; route.&lt;/li&gt;
&lt;li&gt;Response travels back out the same path. Because SGs are stateful, the return traffic is automatically allowed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Security Groups vs NACLs
&lt;/h3&gt;

&lt;p&gt;This is one of the most asked AWS interview questions. Both are virtual firewalls, but they operate at different levels and behave differently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security Group (SG)&lt;/strong&gt; — attached to the &lt;strong&gt;instance/ENI&lt;/strong&gt; (the door). &lt;strong&gt;Stateful&lt;/strong&gt;: if you allow inbound traffic, the response is automatically allowed out (and vice versa). &lt;strong&gt;Allow rules only&lt;/strong&gt; — you can't write a "deny" rule; anything not allowed is implicitly denied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NACL (Network ACL)&lt;/strong&gt; — attached to the &lt;strong&gt;subnet&lt;/strong&gt; (the block gate). &lt;strong&gt;Stateless&lt;/strong&gt;: you must explicitly allow both inbound AND the return outbound traffic. Supports &lt;strong&gt;allow AND deny&lt;/strong&gt; rules, evaluated in numbered order.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Security Group&lt;/th&gt;
&lt;th&gt;NACL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Level&lt;/td&gt;
&lt;td&gt;Instance / ENI&lt;/td&gt;
&lt;td&gt;Subnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Stateful (return traffic auto-allowed)&lt;/td&gt;
&lt;td&gt;Stateless (must allow both directions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rules&lt;/td&gt;
&lt;td&gt;Allow only&lt;/td&gt;
&lt;td&gt;Allow AND Deny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rule evaluation&lt;/td&gt;
&lt;td&gt;All rules evaluated together&lt;/td&gt;
&lt;td&gt;In numbered order, first match wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default behavior&lt;/td&gt;
&lt;td&gt;Deny all inbound, allow all outbound&lt;/td&gt;
&lt;td&gt;Default NACL allows all; custom denies all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use case&lt;/td&gt;
&lt;td&gt;Primary, everyday instance firewall&lt;/td&gt;
&lt;td&gt;Coarse subnet-wide rules, blocking specific IPs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Memorize the one-liner: &lt;strong&gt;"SG = stateful, instance-level, allow-only. NACL = stateless, subnet-level, allow + deny."&lt;/strong&gt; Follow-up: "Can you block a single malicious IP?" → Yes, with a NACL &lt;em&gt;deny&lt;/em&gt; rule (SGs can't deny). In practice, 95% of your work is in Security Groups; NACLs are a secondary, coarse layer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Building VPC with the CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Create the VPC (grab the returned VpcId)&lt;/span&gt;
aws ec2 create-vpc &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cidr-block&lt;/span&gt; 10.0.0.0/16 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag-specifications&lt;/span&gt; &lt;span class="s1"&gt;'ResourceType=vpc,Tags=[{Key=Name,Value=my-vpc}]'&lt;/span&gt;

&lt;span class="c"&gt;# 2. Create a public subnet inside it&lt;/span&gt;
aws ec2 create-subnet &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-0123456789abcdef0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cidr-block&lt;/span&gt; 10.0.1.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--availability-zone&lt;/span&gt; ap-south-1a &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag-specifications&lt;/span&gt; &lt;span class="s1"&gt;'ResourceType=subnet,Tags=[{Key=Name,Value=public-1a}]'&lt;/span&gt;

&lt;span class="c"&gt;# 3. Create a security group in the VPC&lt;/span&gt;
aws ec2 create-security-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-name&lt;/span&gt; web-sg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"Allow HTTP and SSH"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-0123456789abcdef0

&lt;span class="c"&gt;# 4. Allow inbound HTTP (80) from anywhere and SSH (22) from your IP&lt;/span&gt;
aws ec2 authorize-security-group-ingress &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-id&lt;/span&gt; sg-0123456789abcdef0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--protocol&lt;/span&gt; tcp &lt;span class="nt"&gt;--port&lt;/span&gt; 80 &lt;span class="nt"&gt;--cidr&lt;/span&gt; 0.0.0.0/0

aws ec2 authorize-security-group-ingress &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-id&lt;/span&gt; sg-0123456789abcdef0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--protocol&lt;/span&gt; tcp &lt;span class="nt"&gt;--port&lt;/span&gt; 22 &lt;span class="nt"&gt;--cidr&lt;/span&gt; 203.0.113.25/32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Never open SSH (port 22) to &lt;code&gt;0.0.0.0/0&lt;/code&gt; — that exposes your server to the entire internet's brute-force bots. Always restrict to your own IP (&lt;code&gt;/32&lt;/code&gt;) or use a bastion host / AWS Systems Manager Session Manager (no open SSH port at all). This is a top finding in every security audit.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Storage — S3, EBS, EFS
&lt;/h2&gt;

&lt;p&gt;AWS storage comes in three fundamental flavors, and knowing which to reach for is core DevOps knowledge: &lt;strong&gt;object&lt;/strong&gt; storage (S3), &lt;strong&gt;block&lt;/strong&gt; storage (EBS), and &lt;strong&gt;file&lt;/strong&gt; storage (EFS).&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;br&gt;
  &lt;br&gt;
  &lt;/p&gt;
S3 is object storage accessed over HTTPS; EBS is a block volume attached to a single EC2 instance; EFS is a shared NFS file system mountable by many instances simultaneously.





&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;strong&gt;EBS (block)&lt;/strong&gt; = a hard drive plugged into one computer — fast, personal, one machine at a time. &lt;strong&gt;EFS (file)&lt;/strong&gt; = a shared network drive in the office that everyone mounts at once. &lt;strong&gt;S3 (object)&lt;/strong&gt; = a giant warehouse where you drop labelled boxes and fetch them by name via a web address — infinite shelves, but you can't "edit" a box, you replace it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  S3 — Simple Storage Service
&lt;/h3&gt;

&lt;p&gt;S3 is object storage: you store &lt;strong&gt;objects&lt;/strong&gt; (files + metadata) inside &lt;strong&gt;buckets&lt;/strong&gt; (globally-unique named containers). It's accessed over HTTPS via an API/URL, scales infinitely, and is 99.999999999% (11 nines) durable. You don't mount it like a disk — you GET/PUT objects by key.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Bucket names are &lt;strong&gt;globally unique across all AWS accounts on Earth&lt;/strong&gt; — like domain names. If someone took &lt;code&gt;my-bucket&lt;/code&gt;, you can't. Use a prefix like &lt;code&gt;mycompany-prod-logs-2026&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Versioning
&lt;/h4&gt;

&lt;p&gt;Enable versioning and S3 keeps every version of an object. Overwrite or delete? The old version is retained (a "delete marker" is added instead of true deletion). This protects against accidental overwrites and ransomware. Once enabled, it can only be suspended, not fully disabled.&lt;/p&gt;

&lt;h4&gt;
  
  
  Lifecycle Policies
&lt;/h4&gt;

&lt;p&gt;Rules that automatically move or expire objects over time to save money. Example: keep logs in Standard for 30 days, move to Infrequent Access, then to Glacier after 90 days, and delete after a year — all automatic. This is how you control storage costs at scale.&lt;/p&gt;

&lt;h4&gt;
  
  
  Storage Classes
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S3 Standard&lt;/strong&gt; — frequent access, lowest latency. Default for active data, website assets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Standard-IA (Infrequent Access)&lt;/strong&gt; — cheaper storage, but you pay a retrieval fee. For data accessed rarely but needed instantly (backups, older logs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Glacier (Flexible / Deep Archive)&lt;/strong&gt; — dirt cheap archival; retrieval takes minutes to hours. For compliance archives, long-term backups you rarely touch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Intelligent-Tiering&lt;/strong&gt; — AWS auto-moves objects between tiers based on access patterns. Set-and-forget cost optimization.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Don't manually guess tiers for unpredictable access — use &lt;strong&gt;Intelligent-Tiering&lt;/strong&gt; and let AWS optimize. Use explicit &lt;strong&gt;lifecycle policies&lt;/strong&gt; when you KNOW the pattern (e.g., "logs are useless after 90 days").&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Static Website Hosting
&lt;/h4&gt;

&lt;p&gt;S3 can serve a static website (HTML/CSS/JS) directly — perfect for your React build output. Upload the build, enable website hosting, set the index document. Pair it with CloudFront (CDN) + an ACM certificate for HTTPS and global caching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a bucket&lt;/span&gt;
aws s3 mb s3://mycompany-react-app-2026 &lt;span class="nt"&gt;--region&lt;/span&gt; ap-south-1

&lt;span class="c"&gt;# Upload your React production build&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; ./build s3://mycompany-react-app-2026 &lt;span class="nt"&gt;--delete&lt;/span&gt;

&lt;span class="c"&gt;# Enable static website hosting&lt;/span&gt;
aws s3 website s3://mycompany-react-app-2026 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--index-document&lt;/span&gt; index.html &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--error-document&lt;/span&gt; index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Security: Block Public Access and Bucket Policies
&lt;/h4&gt;

&lt;p&gt;Leaky S3 buckets are the most famous cause of data breaches. AWS now enables &lt;strong&gt;Block Public Access&lt;/strong&gt; by default — a master switch that overrides any policy trying to make a bucket public. Access is otherwise controlled by &lt;strong&gt;bucket policies&lt;/strong&gt; (JSON rules on the bucket) and IAM policies (rules on users/roles).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Lock down public access (the safe default)&lt;/span&gt;
aws s3api put-public-access-block &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; mycompany-react-app-2026 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--public-access-block-configuration&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;BlockPublicAcls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;,IgnorePublicAcls&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;,BlockPublicPolicy&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;,RestrictPublicBuckets&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Enable versioning for safety&lt;/span&gt;
aws s3api put-bucket-versioning &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; mycompany-react-app-2026 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--versioning-configuration&lt;/span&gt; &lt;span class="nv"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; For a public website, the modern best practice is NOT to make the S3 bucket public. Instead keep Block Public Access ON and serve through &lt;strong&gt;CloudFront with an Origin Access Control (OAC)&lt;/strong&gt;, so only CloudFront can read the bucket. Public buckets should be a deliberate, rare exception.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  EBS — Elastic Block Store
&lt;/h3&gt;

&lt;p&gt;EBS is a &lt;strong&gt;block storage&lt;/strong&gt; volume — a virtual hard disk you attach to a single EC2 instance. You format it, mount it, and it behaves like a local SSD. It's the root disk of most instances and where databases store data. It lives in &lt;strong&gt;one AZ&lt;/strong&gt; and (traditionally) attaches to &lt;strong&gt;one instance at a time&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a 50 GB gp3 SSD volume in a specific AZ&lt;/span&gt;
aws ec2 create-volume &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume-type&lt;/span&gt; gp3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--size&lt;/span&gt; 50 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--availability-zone&lt;/span&gt; ap-south-1a &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag-specifications&lt;/span&gt; &lt;span class="s1"&gt;'ResourceType=volume,Tags=[{Key=Name,Value=db-data}]'&lt;/span&gt;

&lt;span class="c"&gt;# Attach it to an instance (volume must be in the same AZ as the instance)&lt;/span&gt;
aws ec2 attach-volume &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume-id&lt;/span&gt; vol-0123456789abcdef0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--instance-id&lt;/span&gt; i-0123456789abcdef0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--device&lt;/span&gt; /dev/sdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; EBS and EC2 must be in the &lt;strong&gt;same Availability Zone&lt;/strong&gt; to attach — block storage is AZ-bound. To move data across AZs, take a &lt;strong&gt;snapshot&lt;/strong&gt; (stored in S3 behind the scenes) and create a new volume from it in the target AZ.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  EFS — Elastic File System
&lt;/h3&gt;

&lt;p&gt;EFS is &lt;strong&gt;file storage&lt;/strong&gt; — a managed NFS share that &lt;strong&gt;many EC2 instances mount simultaneously&lt;/strong&gt;, across multiple AZs. It grows and shrinks automatically. Use it when a fleet of servers needs to read/write the same files (shared uploads, CMS content, web cluster assets).&lt;/p&gt;

&lt;h3&gt;
  
  
  Block vs File vs Object — When to Use Which
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;S3 (Object)&lt;/th&gt;
&lt;th&gt;EBS (Block)&lt;/th&gt;
&lt;th&gt;EFS (File)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Object store&lt;/td&gt;
&lt;td&gt;Block volume (virtual disk)&lt;/td&gt;
&lt;td&gt;Shared file system (NFS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access&lt;/td&gt;
&lt;td&gt;HTTPS API / URL&lt;/td&gt;
&lt;td&gt;Mounted to one EC2&lt;/td&gt;
&lt;td&gt;Mounted to many EC2s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attaches to&lt;/td&gt;
&lt;td&gt;Anything over the internet&lt;/td&gt;
&lt;td&gt;1 instance (1 AZ)&lt;/td&gt;
&lt;td&gt;Many instances, multi-AZ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Infinite, automatic&lt;/td&gt;
&lt;td&gt;Fixed size (resize manually)&lt;/td&gt;
&lt;td&gt;Automatic, elastic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can edit in place?&lt;/td&gt;
&lt;td&gt;No — replace whole object&lt;/td&gt;
&lt;td&gt;Yes — random read/write&lt;/td&gt;
&lt;td&gt;Yes — random read/write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Assets, backups, logs, static sites, big data&lt;/td&gt;
&lt;td&gt;OS root disk, databases, single-server data&lt;/td&gt;
&lt;td&gt;Shared storage across a server fleet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; The killer interview line: &lt;strong&gt;"EBS = one disk for one server; EFS = one shared drive for many servers; S3 = object storage accessed over HTTP, not mounted."&lt;/strong&gt; If asked "where do you store database files?" → EBS (low latency block). "Shared files across an Auto Scaling group?" → EFS. "Static assets / backups?" → S3.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Databases — RDS and DynamoDB
&lt;/h2&gt;

&lt;p&gt;As a DevOps engineer, the database is the part nobody wants to babysit at 3am — so AWS gives you &lt;strong&gt;managed&lt;/strong&gt; database services. You stop being a DBA and start being an operator who scripts the boring stuff away.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Running a DB on a raw EC2 instance is like buying a car and also building your own service garage — you patch the engine, change the oil, handle backups. A &lt;em&gt;managed&lt;/em&gt; database (RDS/DynamoDB) is like a chauffeur-driven car with a maintenance contract: you write business logic, AWS handles patching, backups, and failover.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Amazon RDS — Managed Relational
&lt;/h3&gt;

&lt;p&gt;RDS = your familiar SQL world (tables, joins, foreign keys, transactions) but AWS handles provisioning, patching, backups, and failover. You pick an &lt;strong&gt;engine&lt;/strong&gt; and AWS runs it for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Engines:&lt;/strong&gt; PostgreSQL, MySQL, MariaDB, Oracle, SQL Server, and Amazon Aurora (AWS's high-performance MySQL/Postgres-compatible engine).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ (High Availability):&lt;/strong&gt; AWS keeps a synchronous standby replica in a second Availability Zone. If the primary dies, RDS automatically fails over to the standby (DNS endpoint stays the same). This is about &lt;em&gt;survival&lt;/em&gt;, not speed — the standby does not serve reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read Replicas (Scale):&lt;/strong&gt; Asynchronous copies you can read from to offload heavy SELECT traffic (reporting, dashboards). This is about &lt;em&gt;scaling reads&lt;/em&gt;, not failover. You can promote a replica to standalone, but it isn't automatic HA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated backups:&lt;/strong&gt; Daily snapshot + transaction logs enable point-in-time recovery (PITR) within your retention window (up to 35 days). &lt;strong&gt;Manual snapshots&lt;/strong&gt; live until you delete them — good for "before the migration" safety copies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameter groups:&lt;/strong&gt; tune engine settings (e.g. &lt;code&gt;max_connections&lt;/code&gt;) without touching a config file. &lt;strong&gt;Subnet groups:&lt;/strong&gt; tell RDS which subnets (across AZs) it may launch into — almost always your &lt;em&gt;private&lt;/em&gt; subnets.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Multi-AZ and Read Replica are constantly confused in interviews. &lt;strong&gt;Multi-AZ = HA/failover (sync, same endpoint, no read offload). Read Replica = scaling reads (async, separate endpoint).&lt;/strong&gt; You can have both at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; An e-commerce app runs RDS Postgres Multi-AZ for the orders database. The analytics team was hammering it with giant reports, slowing checkout. The fix: add two Read Replicas and point the reporting tool at the replica endpoint. Checkout latency dropped instantly — writes stayed on the primary, reads spread out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Create a Multi-AZ Postgres instance in private subnets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws rds create-db-instance &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; prod-orders-db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-class&lt;/span&gt; db.t3.medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--engine&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--engine-version&lt;/span&gt; 16.3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--master-username&lt;/span&gt; appadmin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--master-user-password&lt;/span&gt; &lt;span class="s1"&gt;'ChangeMe-UseSecretsManager!'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allocated-storage&lt;/span&gt; 50 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-type&lt;/span&gt; gp3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--multi-az&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-subnet-group-name&lt;/span&gt; prod-private-subnets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-security-group-ids&lt;/span&gt; sg-0a1b2c3d4e5f67890 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--backup-retention-period&lt;/span&gt; 7 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-encrypted&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-publicly-accessible&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Never bake the DB password into CLI/Terraform. Use &lt;strong&gt;AWS Secrets Manager&lt;/strong&gt; (RDS can even auto-rotate it) or &lt;code&gt;--manage-master-user-password&lt;/code&gt; so AWS stores and rotates it for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Why Managed Beats Self-Hosted on EC2
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Automated patching, backups, and PITR — no cron jobs you forget about.&lt;/li&gt;
&lt;li&gt;One-flag HA (&lt;code&gt;--multi-az&lt;/code&gt;) instead of hand-building replication and failover scripts.&lt;/li&gt;
&lt;li&gt;Push-button read scaling and storage autoscaling.&lt;/li&gt;
&lt;li&gt;You still own schema, queries, and indexes — i.e. the stuff that's actually your job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Amazon DynamoDB — Managed NoSQL
&lt;/h3&gt;

&lt;p&gt;DynamoDB is a fully managed key-value / document store. No servers, no engine versions, no connection pools — just an API. It delivers &lt;strong&gt;single-digit millisecond latency at any scale&lt;/strong&gt;, which is why it powers shopping carts, session stores, and leaderboards.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Partition key (PK):&lt;/strong&gt; determines which physical partition stores the item — choose a high-cardinality key so traffic spreads evenly (avoid "hot partitions").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort key (SK):&lt;/strong&gt; optional; lets you store many related items under one PK and query ranges (e.g. PK=&lt;code&gt;USER#42&lt;/code&gt;, SK=&lt;code&gt;ORDER#2026-06-01&lt;/code&gt;). PK + SK together form the primary key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-demand capacity:&lt;/strong&gt; pay per request, scales instantly — perfect for spiky or unknown traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provisioned capacity:&lt;/strong&gt; you set read/write capacity units (with optional auto-scaling) — cheaper for steady, predictable load.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb create-table &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--table-name&lt;/span&gt; UserOrders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--attribute-definitions&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UserId,AttributeType&lt;span class="o"&gt;=&lt;/span&gt;S &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OrderId,AttributeType&lt;span class="o"&gt;=&lt;/span&gt;S &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key-schema&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UserId,KeyType&lt;span class="o"&gt;=&lt;/span&gt;HASH &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OrderId,KeyType&lt;span class="o"&gt;=&lt;/span&gt;RANGE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--billing-mode&lt;/span&gt; PAY_PER_REQUEST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;env&lt;/span&gt;,Value&lt;span class="o"&gt;=&lt;/span&gt;prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;HASH&lt;/code&gt; = partition key, &lt;code&gt;RANGE&lt;/code&gt; = sort key in DynamoDB CLI terminology. &lt;code&gt;PAY_PER_REQUEST&lt;/code&gt; = on-demand; switch to &lt;code&gt;PROVISIONED&lt;/code&gt; with &lt;code&gt;--provisioned-throughput&lt;/code&gt; once your traffic is predictable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  RDS vs DynamoDB — When to Pick What
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pick RDS&lt;/strong&gt; when you have relational data, complex joins, transactions, and ad-hoc queries (finance, orders, anything with strong consistency and relationships). &lt;strong&gt;Pick DynamoDB&lt;/strong&gt; when you need massive scale, predictable access patterns by key, ultra-low latency, and zero ops (carts, sessions, IoT, gaming, event logs).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Amazon RDS&lt;/th&gt;
&lt;th&gt;DynamoDB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data model&lt;/td&gt;
&lt;td&gt;Relational (tables, joins)&lt;/td&gt;
&lt;td&gt;NoSQL key-value / document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query flexibility&lt;/td&gt;
&lt;td&gt;Full SQL, ad-hoc queries&lt;/td&gt;
&lt;td&gt;Key-based access; design schema around access patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Vertical + read replicas&lt;/td&gt;
&lt;td&gt;Horizontal, virtually unlimited, automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Low (ms), depends on instance&lt;/td&gt;
&lt;td&gt;Single-digit ms at any scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HA&lt;/td&gt;
&lt;td&gt;Multi-AZ failover&lt;/td&gt;
&lt;td&gt;Built-in, multi-AZ by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops overhead&lt;/td&gt;
&lt;td&gt;Low (you pick instance size, params)&lt;/td&gt;
&lt;td&gt;Near zero (serverless)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Orders, finance, complex relationships&lt;/td&gt;
&lt;td&gt;Carts, sessions, IoT, leaderboards&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; "How do you scale reads on RDS without hurting writes?" → &lt;strong&gt;Read Replicas&lt;/strong&gt;, point read-only traffic at the replica endpoint. "How do you make RDS highly available?" → &lt;strong&gt;Multi-AZ&lt;/strong&gt;. Saying the right one for the right reason instantly signals you understand the difference.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How It All Fits Together
&lt;/h2&gt;

&lt;p&gt;You've met the building blocks one by one — IAM, VPC, subnets, EC2, S3, RDS, Security Groups, gateways, ALB. Now let's snap them together like Lego and deploy a typical web app. This is the mental model interviewers want to hear, and it's the diagram you'll draw on a whiteboard for the rest of your career.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Think of your AWS account as a gated township. &lt;strong&gt;IAM&lt;/strong&gt; = the security desk deciding who enters and what they can touch. &lt;strong&gt;VPC&lt;/strong&gt; = the township's boundary wall. &lt;strong&gt;Subnets&lt;/strong&gt; = individual lanes (some facing the main road = public, some tucked inside = private). &lt;strong&gt;IGW&lt;/strong&gt; = the main gate to the public road. &lt;strong&gt;NAT&lt;/strong&gt; = a one-way courier so inside residents can order online without strangers walking in. &lt;strong&gt;Security Groups&lt;/strong&gt; = the lock on each house's door.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The 3-Tier Architecture (Web / App / Data)
&lt;/h3&gt;

&lt;p&gt;Almost every classic web app splits into three tiers, and AWS maps cleanly onto them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web / presentation tier:&lt;/strong&gt; the public entry point. An &lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt; sits in &lt;em&gt;public subnets&lt;/em&gt; and accepts user traffic on 80/443. Static assets (images, JS bundles, your React build) often live in &lt;strong&gt;S3&lt;/strong&gt; and/or behind a CDN.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App / logic tier:&lt;/strong&gt; your &lt;strong&gt;EC2&lt;/strong&gt; instances (or containers) running the backend, in &lt;em&gt;private subnets&lt;/em&gt;. They never get a public IP — the ALB forwards traffic to them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data tier:&lt;/strong&gt; &lt;strong&gt;RDS&lt;/strong&gt; (and/or DynamoDB) in &lt;em&gt;private subnets&lt;/em&gt;, reachable only from the app tier. Nothing on the internet can touch the database directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each tier lives in its own subnet layer, spread across at least &lt;strong&gt;two Availability Zones&lt;/strong&gt; for resilience, all inside one VPC.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Deployment, Step by Step
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;IAM first.&lt;/strong&gt; Create roles, not long-lived keys. An EC2 &lt;em&gt;instance role&lt;/em&gt; lets the app read from S3 and pull DB credentials from Secrets Manager — no keys on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the VPC.&lt;/strong&gt; Carve a CIDR (e.g. &lt;code&gt;10.0.0.0/16&lt;/code&gt;) and create public + private subnets across two AZs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach an Internet Gateway (IGW)&lt;/strong&gt; to the VPC and route public subnets' &lt;code&gt;0.0.0.0/0&lt;/code&gt; to it. Add a &lt;strong&gt;NAT Gateway&lt;/strong&gt; in a public subnet so private instances can reach the internet outbound (for updates/patches) without being reachable inbound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch EC2 app servers&lt;/strong&gt; in the private subnets, behind an Auto Scaling Group ideally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create the ALB&lt;/strong&gt; in the public subnets, with a target group pointing at the EC2 instances. Health checks decide which instances get traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provision RDS&lt;/strong&gt; (Multi-AZ) in the private subnets via a DB subnet group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire Security Groups (the real glue):&lt;/strong&gt; ALB SG allows 443 from &lt;code&gt;0.0.0.0/0&lt;/code&gt;; EC2 SG allows the app port &lt;em&gt;only from the ALB's SG&lt;/em&gt;; RDS SG allows 5432/3306 &lt;em&gt;only from the EC2 SG&lt;/em&gt;. Referencing SGs by ID (not IP ranges) is the clean, least-privilege way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Point your domain&lt;/strong&gt; (Route 53) at the ALB and attach a TLS cert (ACM) for HTTPS.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Request Flow, End to End
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User's browser resolves your domain via &lt;strong&gt;Route 53&lt;/strong&gt; → gets the ALB's address.&lt;/li&gt;
&lt;li&gt;Request enters the VPC through the &lt;strong&gt;IGW&lt;/strong&gt; and hits the &lt;strong&gt;ALB&lt;/strong&gt; in a public subnet (TLS terminated by ACM cert).&lt;/li&gt;
&lt;li&gt;ALB picks a healthy &lt;strong&gt;EC2&lt;/strong&gt; instance in a private subnet and forwards the request — allowed because the EC2 Security Group trusts the ALB's SG.&lt;/li&gt;
&lt;li&gt;The app reads/writes &lt;strong&gt;RDS&lt;/strong&gt; (private), pulls static files or uploads to &lt;strong&gt;S3&lt;/strong&gt;, and uses its &lt;strong&gt;IAM instance role&lt;/strong&gt; for those calls.&lt;/li&gt;
&lt;li&gt;If the app needs to call an external API, traffic exits via the &lt;strong&gt;NAT Gateway&lt;/strong&gt; — outbound only.&lt;/li&gt;
&lt;li&gt;Response flows back through the ALB to the user. The database and app servers were never exposed to the internet.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A SaaS dashboard runs exactly this shape: ALB in two public subnets, an Auto Scaling Group of EC2 in two private subnets, RDS Postgres Multi-AZ in the data tier, and the React build served from S3 + CloudFront. When a marketing email caused a traffic spike, the ASG added instances and the ALB spread load — zero manual intervention, no downtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Where CI/CD and IaC Fit
&lt;/h3&gt;

&lt;p&gt;You'd never click all of the above by hand in production. &lt;strong&gt;Infrastructure as Code&lt;/strong&gt; — Terraform or CloudFormation — defines the VPC, subnets, SGs, ALB, RDS, and IAM roles as version-controlled files. You &lt;code&gt;plan&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; to create identical environments (dev/staging/prod) repeatably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD&lt;/strong&gt; (GitHub Actions, GitLab CI, CodePipeline) handles the &lt;em&gt;application&lt;/em&gt;: on every git push it builds, tests, and deploys your code to the EC2/containers and pushes the React bundle to S3. IaC builds the house; CI/CD keeps moving the furniture in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; When asked "walk me through deploying a web app on AWS," structure your answer as &lt;strong&gt;tiers (web/app/data) + the security boundaries between them&lt;/strong&gt;. Emphasize that databases and app servers live in &lt;em&gt;private&lt;/em&gt; subnets, the ALB is the only public entry, and Security Groups reference each other by ID. That one paragraph signals senior-level thinking.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Hands-on AWS CLI
&lt;/h2&gt;

&lt;p&gt;The console is great for learning, but DevOps lives in the terminal — scriptable, repeatable, automatable. The AWS CLI is your daily driver. Let's install it, configure credentials, and build a cheat sheet you'll actually reuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install on Ubuntu 24.04
&lt;/h3&gt;

&lt;p&gt;Use the official v2 bundle (the &lt;code&gt;apt&lt;/code&gt; package is often outdated):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; unzip curl
curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install
aws &lt;span class="nt"&gt;--version&lt;/span&gt;   &lt;span class="c"&gt;# confirm: aws-cli/2.x.x ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure Credentials
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;aws configure&lt;/code&gt; walks you through four prompts: Access Key ID, Secret Access Key, default region, and output format (&lt;code&gt;json&lt;/code&gt;, &lt;code&gt;table&lt;/code&gt;, or &lt;code&gt;text&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure
&lt;span class="c"&gt;# AWS Access Key ID [None]: AKIAEXAMPLE...&lt;/span&gt;
&lt;span class="c"&gt;# AWS Secret Access Key [None]: wJalr...EXAMPLEKEY&lt;/span&gt;
&lt;span class="c"&gt;# Default region name [None]: ap-south-1&lt;/span&gt;
&lt;span class="c"&gt;# Default output format [None]: json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Named Profiles and Where Credentials Live
&lt;/h3&gt;

&lt;p&gt;One laptop, many accounts (dev, prod, client). &lt;strong&gt;Named profiles&lt;/strong&gt; keep them separate. Credentials and settings are stored in two plaintext files in your home directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~/.aws/credentials&lt;/code&gt; — the secret keys (per profile).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.aws/config&lt;/code&gt; — region, output format, and SSO/role settings (per profile).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a named profile&lt;/span&gt;
aws configure &lt;span class="nt"&gt;--profile&lt;/span&gt; prod

&lt;span class="c"&gt;# Use it per-command&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--profile&lt;/span&gt; prod

&lt;span class="c"&gt;# Or set it for the whole shell session&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod
aws sts get-caller-identity

&lt;span class="c"&gt;# Inspect the files&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.aws/config
&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.aws/credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; &lt;code&gt;~/.aws/credentials&lt;/code&gt; stores your secret key in &lt;strong&gt;plaintext&lt;/strong&gt;. Never commit this file, never paste keys into Slack, and on shared/CI machines prefer IAM roles or AWS SSO over long-lived keys.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Cheat Sheet — Identity and STS
&lt;/h3&gt;

&lt;p&gt;Your "whoami" — always run this first to confirm which identity/account you're operating as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sts get-caller-identity            &lt;span class="c"&gt;# who am I? which account?&lt;/span&gt;
aws configure list                     &lt;span class="c"&gt;# active profile, region, source&lt;/span&gt;
aws configure list-profiles            &lt;span class="c"&gt;# all configured profiles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cheat Sheet — S3
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls&lt;/span&gt;                                      &lt;span class="c"&gt;# list buckets&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://my-app-assets/                  &lt;span class="c"&gt;# list objects&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; ./build/app.js s3://my-app-assets/   &lt;span class="c"&gt;# upload one file&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; ./build/ s3://my-app-assets/ &lt;span class="nt"&gt;--delete&lt;/span&gt;   &lt;span class="c"&gt;# mirror a dir (great for React builds)&lt;/span&gt;
aws s3 rb s3://old-bucket &lt;span class="nt"&gt;--force&lt;/span&gt;              &lt;span class="c"&gt;# remove bucket + contents&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cheat Sheet — EC2
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Reservations[].Instances[].{ID:InstanceId,State:State.Name,Type:InstanceType,IP:PrivateIpAddress}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
aws ec2 describe-security-groups &lt;span class="nt"&gt;--group-ids&lt;/span&gt; sg-0a1b2c3d
aws ec2 stop-instances  &lt;span class="nt"&gt;--instance-ids&lt;/span&gt; i-0123456789abcdef0
aws ec2 start-instances &lt;span class="nt"&gt;--instance-ids&lt;/span&gt; i-0123456789abcdef0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cheat Sheet — IAM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam list-users
aws iam list-roles &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Roles[].RoleName'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
aws iam list-attached-role-policies &lt;span class="nt"&gt;--role-name&lt;/span&gt; app-ec2-role
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cheat Sheet — CloudWatch Logs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws logs describe-log-groups
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/lambda/my-func &lt;span class="nt"&gt;--follow&lt;/span&gt;         &lt;span class="c"&gt;# live tail, like tail -f&lt;/span&gt;
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /ecs/web-app &lt;span class="nt"&gt;--since&lt;/span&gt; 1h &lt;span class="nt"&gt;--format&lt;/span&gt; short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Two habits that save careers: (1) keep a separate profile per account and run a quick &lt;code&gt;aws sts get-caller-identity&lt;/code&gt; before any destructive command — confirm you're not in prod. (2) For mutating EC2 actions, add &lt;code&gt;--dry-run&lt;/code&gt;: it checks permissions and returns &lt;em&gt;"would have succeeded"&lt;/em&gt; without actually doing anything. Combine with &lt;code&gt;--output table&lt;/code&gt; and &lt;code&gt;--query&lt;/code&gt; (JMESPath) to keep output readable.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cost and Security Guardrails
&lt;/h2&gt;

&lt;p&gt;Two ways to get fired fast in cloud: a surprise five-figure bill, or a leaked access key that lets attackers mine crypto on your account. Both are 100% preventable with guardrails you set up once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost Guardrails
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Billing alerts / AWS Budgets:&lt;/strong&gt; set a monthly budget and get emailed at 50/80/100% — your early-warning system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Explorer:&lt;/strong&gt; visualize spend by service, account, and tag to find what's actually eating money.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tagging strategy:&lt;/strong&gt; tag every resource with &lt;code&gt;env&lt;/code&gt;, &lt;code&gt;team&lt;/code&gt;, &lt;code&gt;project&lt;/code&gt;, &lt;code&gt;owner&lt;/code&gt;. Without tags you can't attribute cost or clean up safely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right-sizing:&lt;/strong&gt; most instances run at &amp;lt;20% CPU. Use Compute Optimizer to downsize over-provisioned EC2/RDS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot and Savings Plans:&lt;/strong&gt; Spot Instances (up to ~90% off) for fault-tolerant/batch workloads; Savings Plans / Reserved Instances for steady baseline compute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kill idle resources&lt;/strong&gt; — the silent money drains:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unattached Elastic IPs&lt;/strong&gt; — an EIP not associated with a running instance is billed hourly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateways&lt;/strong&gt; — billed per hour AND per GB; idle ones in dev accounts are a classic leak.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unattached EBS volumes and old snapshots&lt;/strong&gt; — keep getting billed after the instance is long gone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Idle load balancers and stopped-but-not-terminated dev instances.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; Free-tier traps: the free tier is &lt;strong&gt;12 months and capped&lt;/strong&gt;. NAT Gateways, Elastic IPs, inter-AZ data transfer, and provisioned RDS storage are &lt;em&gt;not&lt;/em&gt; free. A forgotten &lt;code&gt;t2.micro&lt;/code&gt; beyond the cap, or one NAT Gateway left running, quietly bills you for months. Always set a Budget on day one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Set a $50 monthly budget with an 80% email alert via CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; budget.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{ "BudgetName": "monthly-50usd", "BudgetLimit": {"Amount":"50","Unit":"USD"},
  "TimeUnit": "MONTHLY", "BudgetType": "COST" }
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; notify.json &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[ { "Notification": {"NotificationType":"ACTUAL","ComparisonOperator":"GREATER_THAN",
      "Threshold":80,"ThresholdType":"PERCENTAGE"},
    "Subscribers":[{"SubscriptionType":"EMAIL","Address":"your@email.com"}] } ]
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;aws budgets create-budget &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--account-id&lt;/span&gt; 123456789012 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--budget&lt;/span&gt; file://budget.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--notifications-with-subscribers&lt;/span&gt; file://notify.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Set your very first Budget before launching anything. Add a second alert with &lt;code&gt;NotificationType: FORECASTED&lt;/code&gt; so AWS warns you when it &lt;em&gt;predicts&lt;/em&gt; you'll blow the budget — not just after you already have.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Security Guardrails
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MFA on the root account&lt;/strong&gt; — then lock the root keys away and never use root for daily work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege&lt;/strong&gt; — grant only the permissions a role/user actually needs; start narrow and widen if required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never commit access keys&lt;/strong&gt; — no keys in Git, Dockerfiles, or env files in repos. Use a &lt;code&gt;.gitignore&lt;/code&gt; and a secrets scanner in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roles, not keys&lt;/strong&gt; — EC2/Lambda/ECS use IAM roles for temporary, auto-rotating credentials. Long-lived keys are the #1 breach vector.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption&lt;/strong&gt; — at rest with &lt;strong&gt;KMS&lt;/strong&gt; (enable on S3, EBS, RDS), in transit with TLS everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudTrail + GuardDuty&lt;/strong&gt; — CloudTrail logs every API call (the audit trail); GuardDuty uses ML to flag suspicious behavior (e.g. crypto-mining, anomalous API calls).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Groups least-open&lt;/strong&gt; — never &lt;code&gt;0.0.0.0/0&lt;/code&gt; on SSH/RDP/DB ports. Reference SGs by ID and open only what's needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Block Public Access&lt;/strong&gt; — keep it ON at the account level unless a bucket is deliberately a public website.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; The classic disaster: a developer commits an access key to a public GitHub repo. Bots scan GitHub within &lt;em&gt;minutes&lt;/em&gt; and spin up expensive GPU instances for crypto mining. Use IAM roles instead of keys, and enable a secret scanner so a key never reaches the repo in the first place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Enable a virtual MFA device for an IAM user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the virtual MFA device&lt;/span&gt;
aws iam create-virtual-mfa-device &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--virtual-mfa-device-name&lt;/span&gt; my-mfa &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--outfile&lt;/span&gt; /tmp/qr.png &lt;span class="nt"&gt;--bootstrap-method&lt;/span&gt; QRCodePNG

&lt;span class="c"&gt;# Scan QR in your authenticator app, then enable with two consecutive codes&lt;/span&gt;
aws iam enable-mfa-device &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-name&lt;/span&gt; myuser &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--serial-number&lt;/span&gt; arn:aws:iam::123456789012:mfa/my-mfa &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--authentication-code1&lt;/span&gt; 123456 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--authentication-code2&lt;/span&gt; 654321
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Turn ON account-wide S3 public access block&lt;/span&gt;
aws s3control put-public-access-block &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--account-id&lt;/span&gt; 123456789012 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--public-access-block-configuration&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;BlockPublicAcls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;,IgnorePublicAcls&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;,BlockPublicPolicy&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;,RestrictPublicBuckets&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; A simple priority order for any new account: (1) MFA on root, (2) a Budget alert, (3) CloudTrail on, (4) S3 Block Public Access on, (5) IAM roles instead of keys. Five steps, fifteen minutes, and you've dodged the most common cost and security incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Prefer a day-by-day path? This is covered in &lt;a href="https://opscanopy.com/mission-90/" rel="noopener noreferrer"&gt;&lt;strong&gt;Mission 90 Days 46–65&lt;/strong&gt;&lt;/a&gt; — a free 90-day guided DevOps program with browser terminal missions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Running containers on AWS? Continue with &lt;a href="https://opscanopy.com/learn/guides/kubernetes-for-devops/" rel="noopener noreferrer"&gt;Kubernetes for DevOps&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>iam</category>
    </item>
    <item>
      <title>How to Validate .gitlab-ci.yml Before You Push</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Tue, 11 Aug 2026 15:12:10 +0000</pubDate>
      <link>https://dev.to/opscanopy/how-to-validate-gitlab-ciyml-before-you-push-374j</link>
      <guid>https://dev.to/opscanopy/how-to-validate-gitlab-ciyml-before-you-push-374j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fvalidate-gitlab-ci-yml-hero.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fvalidate-gitlab-ci-yml-hero.svg" alt="GitLab CI validator checking a .gitlab-ci.yml for YAML and pipeline errors before push" width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You change one line in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;, push, and switch to something else. Two minutes later the pipeline goes red — not because the build broke, but because a job points at a &lt;code&gt;stage&lt;/code&gt; you renamed last week. You fix the typo, push again, wait again. This is the loop, and the only way out is to validate &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; the commit lands, not after the runner tells you.&lt;/p&gt;

&lt;p&gt;The frustrating part is that GitLab already knows your config is broken the instant it parses it. It just doesn't tell you until you've pushed and burned a CI minute. The fix is to run that same check locally, in the browser, before you ever &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The push-and-pray loop
&lt;/h2&gt;

&lt;p&gt;Here's the shape of the problem. You edit a job, push, and let GitLab be your linter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add .gitlab-ci.yml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"split deploy into staging + prod"&lt;/span&gt;
git push
&lt;span class="c"&gt;# wait for the runner to pick up the pipeline...&lt;/span&gt;
&lt;span class="c"&gt;# pipeline failed: "chosen stage prod does not exist"&lt;/span&gt;
git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"fix: declare prod stage"&lt;/span&gt;
git push
&lt;span class="c"&gt;# wait again...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each round trip is a commit you didn't want, a runner slot you didn't need, and a context switch that costs more than the typo did. The errors that cause this almost never need a runner to detect. They're visible the moment the YAML is parsed and the job graph is resolved — which is exactly what a validator does locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of errors: YAML syntax vs structural
&lt;/h2&gt;

&lt;p&gt;When GitLab rejects a pipeline, the failure is one of two categories, and they have completely different fixes.&lt;/p&gt;

&lt;p&gt;The first is a &lt;strong&gt;YAML syntax error&lt;/strong&gt;: the file isn't valid YAML at all, so nothing downstream can read it. The second is a &lt;strong&gt;structural error&lt;/strong&gt;: the YAML parses fine, but the &lt;em&gt;pipeline&lt;/em&gt; it describes is invalid — a job with no script, a stage that was never declared, a &lt;code&gt;needs&lt;/code&gt; pointing at a job that doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# YAML error — the parser can't even build a document&lt;/span&gt;
&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make test&lt;/span&gt;      &lt;span class="c1"&gt;# inconsistent indentation: parser bails here&lt;/span&gt;

&lt;span class="c1"&gt;# Structural error — valid YAML, invalid pipeline&lt;/span&gt;
&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;        &lt;span class="c1"&gt;# "prod" is not in stages: → GitLab refuses to run it&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./deploy.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid YAML is only half the job. The &lt;a href="https://opscanopy.com/gitlab-ci-validator/" rel="noopener noreferrer"&gt;GitLab CI Validator&lt;/a&gt; checks both in one pass: it parses the YAML first, and only if that succeeds does it run the structural checks against your jobs. If the parse fails, you get a single line-referenced error and nothing else — there's no point reporting "undefined stage" on a document that didn't parse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjaeykj23susr7bym6coh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjaeykj23susr7bym6coh.webp" alt="Illustration: a glowing .gitlab-ci.yml scanned by CI lint tools, yamllint and editor checks, with OK and error verdicts flowing toward a draft merge request" width="799" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  YAML errors that bite: indentation, tabs, duplicate keys
&lt;/h2&gt;

&lt;p&gt;YAML is whitespace-significant, and CI config is exactly the kind of nested structure where that bites. The classic GitLab error message — &lt;code&gt;did not find expected key&lt;/code&gt; — is almost always one of these.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;              &lt;span class="c1"&gt;# a literal TAB instead of spaces → parse error&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DEPLOY_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
  &lt;span class="na"&gt;DEPLOY_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;       &lt;span class="c1"&gt;# duplicate key — the first value is silently lost&lt;/span&gt;

&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;deploy_steps&lt;/span&gt;  &lt;span class="c1"&gt;# anchor defined...&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./deploy.sh&lt;/span&gt;
&lt;span class="na"&gt;rollback&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*deploy_step&lt;/span&gt;   &lt;span class="c1"&gt;# ...but referenced with a typo → "unknown alias"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A browser validator parses with a real YAML reader, so it reports the exact line where the structure broke. When you paste config and the result is &lt;code&gt;Could not parse YAML: ... (line 4, column 2)&lt;/code&gt;, that's the parser telling you precisely where to look — re-indent, swap the tab for spaces, or fix the anchor name, and re-validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structural errors GitLab catches late: undefined stages, jobs with no script, bad needs/extends
&lt;/h2&gt;

&lt;p&gt;These are the ones that make you wait for a runner only to be told the pipeline never started. They're the real reason to validate GitLab CI before push. The validator models the rules from GitLab's &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; keyword reference and flags each one with the offending job, the line, and the fix.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fvalidate-gitlab-ci-yml-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fvalidate-gitlab-ci-yml-diagram.svg" alt="A validation pipeline flow: paste .gitlab-ci.yml, parse YAML, run structural checks, then show valid or a list of errors" width="1200" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A job with no executable surface.&lt;/strong&gt; Every visible job has to &lt;em&gt;do&lt;/em&gt; something: run commands with &lt;code&gt;script:&lt;/code&gt; (or the newer &lt;code&gt;run:&lt;/code&gt;), start a downstream pipeline with &lt;code&gt;trigger:&lt;/code&gt;, or inherit one of those via &lt;code&gt;extends:&lt;/code&gt;. A job with none of them is rejected with the familiar "job config should implement a script: or a trigger: keyword."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ERROR — empty-job defines no script, run, trigger, or extends&lt;/span&gt;
&lt;span class="na"&gt;empty-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="c1"&gt;# nothing here → GitLab won't run it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that an &lt;em&gt;empty&lt;/em&gt; &lt;code&gt;script: []&lt;/code&gt; or &lt;code&gt;script: ""&lt;/code&gt; counts as missing too — the validator treats only a non-empty command string or list as a real executable surface, the same way GitLab does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A stage that isn't declared.&lt;/strong&gt; If a job's &lt;code&gt;stage:&lt;/code&gt; isn't in your &lt;code&gt;stages:&lt;/code&gt; list (or one of the five defaults: &lt;code&gt;.pre&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;deploy&lt;/code&gt;, &lt;code&gt;.post&lt;/code&gt;), GitLab doesn't know when to run it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;

&lt;span class="na"&gt;release-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;      &lt;span class="c1"&gt;# ERROR — "release" is not in stages:&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a subtle variant the validator also catches: a job that &lt;em&gt;omits&lt;/em&gt; &lt;code&gt;stage:&lt;/code&gt; defaults to the implicit &lt;code&gt;test&lt;/code&gt; stage. If you declared a custom &lt;code&gt;stages:&lt;/code&gt; list that doesn't include &lt;code&gt;test&lt;/code&gt;, that job is now pointing at a stage you never declared — and GitLab fails with "chosen stage test does not exist."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;needs&lt;/code&gt; / &lt;code&gt;dependencies&lt;/code&gt; / &lt;code&gt;extends&lt;/code&gt; pointing at a job that doesn't exist.&lt;/strong&gt; Every name in &lt;code&gt;needs:&lt;/code&gt;, &lt;code&gt;dependencies:&lt;/code&gt;, or &lt;code&gt;extends:&lt;/code&gt; has to resolve to a real job or hidden &lt;code&gt;.template&lt;/code&gt; in the same file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;compile&lt;/span&gt;          &lt;span class="c1"&gt;# ERROR — no job named "compile"&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.base&lt;/span&gt;       &lt;span class="c1"&gt;# ERROR — no template named ".base"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;make test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validator builds the set of every job id and every &lt;code&gt;.template&lt;/code&gt;, then checks each reference against it. Rename a template and forget to update an &lt;code&gt;extends:&lt;/code&gt;, and it tells you which job broke before the runner does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An invalid &lt;code&gt;when:&lt;/code&gt; or a non-list &lt;code&gt;rules:&lt;/code&gt;.&lt;/strong&gt; The &lt;code&gt;when:&lt;/code&gt; keyword only accepts &lt;code&gt;on_success&lt;/code&gt;, &lt;code&gt;on_failure&lt;/code&gt;, &lt;code&gt;always&lt;/code&gt;, &lt;code&gt;manual&lt;/code&gt;, &lt;code&gt;delayed&lt;/code&gt;, or &lt;code&gt;never&lt;/code&gt;. And &lt;code&gt;rules:&lt;/code&gt; has to be a YAML &lt;em&gt;list&lt;/em&gt; of rule objects — a bare mapping is a common mistake that silently changes when a job runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sometimes&lt;/span&gt;      &lt;span class="c1"&gt;# ERROR — not an allowed when value&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$CI_COMMIT_TAG'&lt;/span&gt;   &lt;span class="c1"&gt;# ERROR — rules must be a list, not a mapping&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./deploy.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also surfaces lower-severity advice: legacy &lt;code&gt;only&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt; get an info note recommending &lt;code&gt;rules:&lt;/code&gt; (the two can't be combined in one job), a top-level key that's one edit away from a reserved keyword — say &lt;code&gt;varables:&lt;/code&gt; or &lt;code&gt;beforescript:&lt;/code&gt; — gets a misspelling warning, and malformed &lt;code&gt;image:&lt;/code&gt;/&lt;code&gt;services:&lt;/code&gt; shapes are flagged as errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating before you push: GitLab CI Lint vs an in-browser validator
&lt;/h2&gt;

&lt;p&gt;GitLab ships its own checker — CI Lint, inside the pipeline editor. It's authoritative: it resolves &lt;code&gt;include:&lt;/code&gt; files and project-level CI/CD variables, which a client-side tool can't see. But it has a cost: it requires a project and a sign-in. You can't lint a snippet from a code review, a config you're drafting offline, or a proprietary pipeline you'd rather not paste into a hosted form.&lt;/p&gt;

&lt;p&gt;So what does an in-browser validator actually check? Based on the engine, the flow is deterministic and entirely local:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parse the YAML.&lt;/strong&gt; Any failure returns a single line-referenced error and stops — no structural findings on an unparseable document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split the top level&lt;/strong&gt; into global keywords (&lt;code&gt;stages&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt;, &lt;code&gt;variables&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;services&lt;/code&gt;…), visible jobs, and hidden &lt;code&gt;.templates&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolve the stages&lt;/strong&gt; — your declared &lt;code&gt;stages:&lt;/code&gt; list, or the five defaults — into the set every job's &lt;code&gt;stage:&lt;/code&gt; is checked against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check every job&lt;/strong&gt; for an executable surface, a known stage, real &lt;code&gt;needs&lt;/code&gt;/&lt;code&gt;extends&lt;/code&gt;/&lt;code&gt;dependencies&lt;/code&gt; targets, a valid &lt;code&gt;when:&lt;/code&gt;, a list-shaped &lt;code&gt;rules:&lt;/code&gt;, and sane &lt;code&gt;image&lt;/code&gt;/&lt;code&gt;services&lt;/code&gt; shapes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank by severity&lt;/strong&gt; — errors first, then warnings, then info — each with the line and a concrete remediation. It never throws; a parse failure is reported, not crashed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The honest framing: a clean result in the browser is strong pre-push confidence on &lt;em&gt;structure and syntax&lt;/em&gt;. It catches the entire class of mistakes that fail a pipeline before any job runs. For absolute certainty on a config that uses &lt;code&gt;include:&lt;/code&gt; or project variables, confirm with GitLab's own CI Lint once you've pushed to a project — but use the in-browser pass to make that push count.&lt;/p&gt;

&lt;p&gt;If you also run GitHub Actions, the same idea applies there: the &lt;a href="https://opscanopy.com/github-actions-validator/" rel="noopener noreferrer"&gt;GitHub Actions Validator&lt;/a&gt; finds YAML and security issues in your workflow files, and the &lt;a href="https://opscanopy.com/github-actions-expression-tester/" rel="noopener noreferrer"&gt;GitHub Actions Expression Tester&lt;/a&gt; evaluates those &lt;code&gt;${{ … }}&lt;/code&gt; expressions before you push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wire it into your workflow
&lt;/h2&gt;

&lt;p&gt;The validator is a paste-and-check tool, but the habit you want is "never push CI config you haven't validated." A pre-commit hook makes that automatic for the YAML half — catch the parse errors before the commit even forms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# .git/hooks/pre-commit — block a commit if .gitlab-ci.yml isn't valid YAML&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="k"&gt;if &lt;/span&gt;git diff &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'^\.gitlab-ci\.yml$'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="c"&gt;# Fail fast on a syntax error before the commit lands.&lt;/span&gt;
  python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys, yaml; yaml.safe_load(open('.gitlab-ci.yml'))"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✗ .gitlab-ci.yml is not valid YAML — commit blocked"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✓ .gitlab-ci.yml parses — paste it into the validator for structural checks"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A local YAML parse catches the indentation-and-tabs class instantly. For the structural class — undefined stages, broken &lt;code&gt;needs&lt;/code&gt;, jobs with no script — paste the file into the browser validator before you push. The two together cover both error categories from the second section, and neither needs a runner.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the loop you actually want&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git add .gitlab-ci.yml          &lt;span class="c"&gt;# pre-commit hook checks YAML&lt;/span&gt;
&lt;span class="c"&gt;# paste .gitlab-ci.yml → validator → 0 errors&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"split deploy into staging + prod"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push                        &lt;span class="c"&gt;# green on the first try&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Validate it now
&lt;/h2&gt;

&lt;p&gt;The next time you touch &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;, don't let the runner be the first thing to read it. Paste the file into the &lt;a href="https://opscanopy.com/gitlab-ci-validator/" rel="noopener noreferrer"&gt;GitLab CI Validator&lt;/a&gt; and you'll get the YAML errors and the structural mistakes — undefined stages, jobs with no script, broken &lt;code&gt;needs&lt;/code&gt;/&lt;code&gt;extends&lt;/code&gt;, invalid &lt;code&gt;when:&lt;/code&gt; — in one pass, with the line and the fix for each. It runs entirely in your browser: no project, no login, and nothing uploaded, so it's safe for internal pipelines.&lt;/p&gt;

&lt;p&gt;If you've ever pushed a CI change and hoped it worked, this is the step that was missing.&lt;/p&gt;

</description>
      <category>gitlabci</category>
      <category>cicd</category>
      <category>yaml</category>
    </item>
    <item>
      <title>Unit Testing Loki Alert Rules: the gap promtool leaves</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Tue, 11 Aug 2026 15:11:36 +0000</pubDate>
      <link>https://dev.to/opscanopy/unit-testing-loki-alert-rules-the-gap-promtool-leaves-eee</link>
      <guid>https://dev.to/opscanopy/unit-testing-loki-alert-rules-the-gap-promtool-leaves-eee</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Funit-testing-loki-alert-rules-hero.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Funit-testing-loki-alert-rules-hero.svg" alt="Unit testing Loki alert rules: a promtool-style test loop for LogQL alerting rules" width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run Prometheus, you already have a safety net for your alerting logic: &lt;code&gt;promtool test rules&lt;/code&gt;. You feed it a series of synthetic samples, declare what should fire and when, and CI tells you the moment a refactor breaks an alert. It’s the difference between catching a broken page rule in code review and discovering it during an incident.&lt;/p&gt;

&lt;p&gt;Grafana Loki has no equivalent. You can write LogQL alerting and recording rules that look almost identical to their Prometheus cousins, load them into the ruler, and ship them — but there’s no first-class way to assert that a given stream of logs produces the alert you expect. The gap is real, it’s long-standing, and it’s exactly the kind of thing that bites you at 3&amp;nbsp;a.m.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why promtool does not cover Loki
&lt;/h2&gt;

&lt;p&gt;The instinctive move is to reach for &lt;code&gt;promtool&lt;/code&gt; and point it at your Loki rules. It doesn’t work, and the reason is fundamental rather than cosmetic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;promtool test rules&lt;/code&gt; evaluates PromQL against a synthetic &lt;strong&gt;time series&lt;/strong&gt; database. You describe metrics with the &lt;code&gt;series&lt;/code&gt;/&lt;code&gt;values&lt;/code&gt; syntax and the tool replays them through the rule engine. But a Loki alert rule doesn’t start from metrics — it starts from &lt;strong&gt;log lines&lt;/strong&gt;. A rule like &lt;code&gt;count_over_time({app="api"} |= "panic" [5m]) &amp;gt; 0&lt;/code&gt; has to run a LogQL pipeline (stream selector, line filter, label extraction, then a metric aggregation) over raw log entries before there’s any series to evaluate. promtool has no concept of a log stream, no LogQL parser, and no way to materialise the intermediate metrics the way Loki’s query engine does. Feeding it Loki rules either errors out or silently tests the wrong thing.&lt;/p&gt;

&lt;p&gt;So the test surface that matters for Loki — “given these log lines, does this LogQL rule fire?” — is precisely the surface promtool can’t reach.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Funit-testing-loki-alert-rules-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Funit-testing-loki-alert-rules-diagram.svg" alt="A Loki alert rule unit test loop: synthetic log streams evaluated at a chosen time and asserted against the expected alerts" width="1200" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;LogQL alert rules are deceptively easy to get subtly wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A line filter that matches more (or less) than you think because of an unescaped regex or a missing word boundary.&lt;/li&gt;
&lt;li&gt;A label that you &lt;code&gt;unwrap&lt;/code&gt; or &lt;code&gt;label_format&lt;/code&gt; incorrectly, so the aggregation groups the wrong way.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;[5m]&lt;/code&gt; range and a &lt;code&gt;for: 10m&lt;/code&gt; clause that interact so the alert never has enough data to fire, or fires far later than intended.&lt;/li&gt;
&lt;li&gt;A recording rule whose output series silently changes labels after a pipeline edit, breaking every downstream alert that selects on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are caught by YAML linting or a schema check. They’re &lt;strong&gt;behavioural&lt;/strong&gt; bugs, and the only honest way to catch them is to run the rule against representative input and assert on the output. Without a test harness, that verification happens manually, infrequently, and usually after something has already paged the wrong team — or failed to page the right one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Loki rule unit test should look like
&lt;/h2&gt;

&lt;p&gt;The model promtool established is the right one; it just needs a log-shaped input. Instead of synthetic series, a Loki rule test should accept synthetic &lt;strong&gt;streams&lt;/strong&gt; (a set of labels plus timestamped log lines), evaluate the rule at a chosen time, and assert on the alerts produced — something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# loki-rule-tests.yaml&lt;/span&gt;
&lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;panic in api logs fires PanicDetected&lt;/span&gt;
    &lt;span class="c1"&gt;# Synthetic log streams replayed through the LogQL engine.&lt;/span&gt;
    &lt;span class="na"&gt;input_streams&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{app="api",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env="prod"}'&lt;/span&gt;
        &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-06-08T10:00:30Z"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level=info&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;msg=ok"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-06-08T10:01:10Z"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level=error&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;msg=panic:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nil&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;map"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-06-08T10:02:40Z"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level=error&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;msg=panic:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nil&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;map"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Evaluate the rule group at this instant.&lt;/span&gt;
    &lt;span class="na"&gt;eval_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-08T10:05:00Z&lt;/span&gt;

    &lt;span class="na"&gt;alert_rule_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alertname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PanicDetected&lt;/span&gt;
        &lt;span class="c1"&gt;# What we expect the ruler to emit at eval_time.&lt;/span&gt;
        &lt;span class="na"&gt;exp_alerts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;exp_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
              &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
              &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;critical&lt;/span&gt;
            &lt;span class="na"&gt;exp_annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Panic&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;detected&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;api"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule under test is the same rule you ship to the ruler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-alerts&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PanicDetected&lt;/span&gt;
        &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;count_over_time({app="api", env="prod"} |= "panic" [5m]) &amp;gt; 1&lt;/span&gt;
        &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0m&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;critical&lt;/span&gt;
        &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Panic&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;detected&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$labels.app&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read together, the test says: given two panic lines in the five-minute window before &lt;code&gt;10:05&lt;/code&gt;, the &lt;code&gt;count_over_time(...) &amp;gt; 1&lt;/code&gt; expression should be true, and the ruler should emit a &lt;code&gt;PanicDetected&lt;/code&gt; alert carrying &lt;code&gt;severity=critical&lt;/code&gt; and the &lt;code&gt;app&lt;/code&gt;/&lt;code&gt;env&lt;/code&gt; labels from the stream. Flip the input to a single panic line, or move one entry outside the &lt;code&gt;[5m]&lt;/code&gt; window, and &lt;code&gt;exp_alerts&lt;/code&gt; becomes empty — the test now guards both the firing and the not-firing case.&lt;/p&gt;

&lt;p&gt;This is the shape every team that has asked for it on the Loki tracker keeps describing — see the long-running requests in Loki issues &lt;a href="https://github.com/grafana/loki/issues/7655" rel="noopener noreferrer"&gt;#7655&lt;/a&gt; and &lt;a href="https://github.com/grafana/loki/issues/16659" rel="noopener noreferrer"&gt;#16659&lt;/a&gt;, where the community has repeatedly pointed out that a promtool-style unit test for LogQL rules simply doesn’t exist yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the gap today
&lt;/h2&gt;

&lt;p&gt;You don’t have to wait for upstream to ship this. &lt;strong&gt;AlertLint&lt;/strong&gt; runs exactly this test loop in your browser: paste your Loki alerting and recording rules, define &lt;code&gt;input_streams&lt;/code&gt;, declare your &lt;code&gt;exp_alerts&lt;/code&gt;, and assert pass or fail before the rule ever reaches the ruler. Everything evaluates client-side — your rules and logs never leave the device — so you can wire it into review without touching infrastructure or sending data anywhere.&lt;/p&gt;

&lt;p&gt;If you’ve ever shipped a Loki alert and hoped it worked, this is the missing step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opscanopy.com/loki-alert-rule-tester/" rel="noopener noreferrer"&gt;Try AlertLint — the Loki alert rule tester →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>loki</category>
      <category>observability</category>
      <category>testing</category>
    </item>
    <item>
      <title>Prometheus relabel_configs Explained: A Practical Guide</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Sun, 09 Aug 2026 05:50:44 +0000</pubDate>
      <link>https://dev.to/opscanopy/prometheus-relabelconfigs-explained-a-practical-guide-598n</link>
      <guid>https://dev.to/opscanopy/prometheus-relabelconfigs-explained-a-practical-guide-598n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fprometheus-relabel-configs-explained-hero.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fprometheus-relabel-configs-explained-hero.svg" alt="Diagram of a Prometheus relabel_configs pipeline showing source_labels joined into a value, matched against an anchored regex, and an action like replace, keep, drop, labelmap or hashmod rewriting the output labels." width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A target you expected to scrape never shows up in Prometheus. No error in the logs, no failed scrape, nothing red on the targets page — the series just isn't there. You add &lt;code&gt;--log.level=debug&lt;/code&gt;, restart, squint at the output, and eventually find it: a &lt;code&gt;keep&lt;/code&gt; rule three lines into your &lt;code&gt;relabel_configs&lt;/code&gt; quietly dropped the target because the regex didn't match the way you assumed. That silent failure is the whole reason &lt;code&gt;relabel_configs&lt;/code&gt; deserves a careful read. Prometheus relabeling rewrites, keeps, or drops targets and their labels, and when it's wrong it doesn't complain — it just discards your metrics.&lt;/p&gt;

&lt;p&gt;This guide walks through Prometheus relabeling from the ground up: what it does, the fields every rule is built from, and each action with a small example. The semantics here match exactly what the engine in the &lt;a href="https://opscanopy.com/prometheus-relabel-tester/" rel="noopener noreferrer"&gt;Prometheus Relabel Tester&lt;/a&gt; implements, so you can paste any snippet below into it and watch the labels change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What relabeling actually does
&lt;/h2&gt;

&lt;p&gt;Relabeling runs over a label set and produces a new label set. That's it. Every target Prometheus discovers arrives as a bag of labels — its address, its job, and a pile of &lt;code&gt;__meta_*&lt;/code&gt; labels from service discovery. Before the scrape happens, your &lt;code&gt;relabel_configs&lt;/code&gt; rules run top to bottom over those labels. Each rule sees the output of the one before it.&lt;/p&gt;

&lt;p&gt;A rule can do one of three things to that label set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite&lt;/strong&gt; a label (or create one) — &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;labelmap&lt;/code&gt;, &lt;code&gt;lowercase&lt;/code&gt;, &lt;code&gt;uppercase&lt;/code&gt;, &lt;code&gt;hashmod&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop the whole target&lt;/strong&gt; so it's never scraped — &lt;code&gt;keep&lt;/code&gt;, &lt;code&gt;drop&lt;/code&gt;, &lt;code&gt;keepequal&lt;/code&gt;, &lt;code&gt;dropequal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remove individual labels&lt;/strong&gt; by name — &lt;code&gt;labeldrop&lt;/code&gt;, &lt;code&gt;labelkeep&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.0.5:8080"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;relabel_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__address__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;instance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this rule runs, the target carries an &lt;code&gt;instance&lt;/code&gt; label copied from &lt;code&gt;__address__&lt;/code&gt;. Nothing errored, nothing was dropped — one label was rewritten. That is the entire job of relabeling, repeated rule by rule.&lt;/p&gt;

&lt;p&gt;There are two places relabeling runs. &lt;code&gt;relabel_configs&lt;/code&gt; runs &lt;em&gt;before&lt;/em&gt; the scrape, on the target's discovery labels, and can keep or drop whole targets. &lt;code&gt;metric_relabel_configs&lt;/code&gt; runs &lt;em&gt;after&lt;/em&gt; the scrape, on every sample's labels, and is used to drop or rewrite individual time series. Same actions, same semantics — only the timing and the input differ.&lt;/p&gt;

&lt;h2&gt;
  
  
  The building blocks: source_labels, separator, regex, modulus, target_label, replacement, action
&lt;/h2&gt;

&lt;p&gt;Every relabel rule is assembled from the same handful of fields. Most have defaults, so a rule rarely sets all of them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# which label values to read&lt;/span&gt;
  &lt;span class="na"&gt;separator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;"&lt;/span&gt;                   &lt;span class="c1"&gt;# how to join them (default ";")&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(.*);(.*)"&lt;/span&gt;               &lt;span class="c1"&gt;# pattern to match the joined value (default "(.*)")&lt;/span&gt;
  &lt;span class="na"&gt;modulus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;                       &lt;span class="c1"&gt;# only for hashmod&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt;           &lt;span class="c1"&gt;# label to write (required by some actions)&lt;/span&gt;
  &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$1-$2"&lt;/span&gt;             &lt;span class="c1"&gt;# value to write, with $1/${1} expansion (default "$1")&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replace&lt;/span&gt;                  &lt;span class="c1"&gt;# what to do (default "replace")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how a rule processes that. Prometheus reads each name in &lt;code&gt;source_labels&lt;/code&gt;, looks up its value (a missing label reads as the empty string), and joins them with &lt;code&gt;separator&lt;/code&gt;. The default separator is a single semicolon, so &lt;code&gt;source_labels: [job, instance]&lt;/code&gt; on &lt;code&gt;job="api"&lt;/code&gt;, &lt;code&gt;instance="10.0.0.1:9090"&lt;/code&gt; produces the joined value &lt;code&gt;api;10.0.0.1:9090&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That joined value is matched against &lt;code&gt;regex&lt;/code&gt;. The one detail that catches everyone: &lt;strong&gt;the regex is fully anchored&lt;/strong&gt;. Prometheus wraps your pattern as &lt;code&gt;^(?:your-regex)$&lt;/code&gt;, so it must match the &lt;em&gt;entire&lt;/em&gt; joined value, not just part of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This does NOT match "api-server" — the regex must match the whole value.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;regex: api&lt;/code&gt; rule will not keep a target whose &lt;code&gt;job&lt;/code&gt; is &lt;code&gt;api-server&lt;/code&gt;, because &lt;code&gt;^(?:api)$&lt;/code&gt; only matches the literal string &lt;code&gt;api&lt;/code&gt;. You'd need &lt;code&gt;api.*&lt;/code&gt; or &lt;code&gt;(api.*)&lt;/code&gt;. This single fact explains the majority of "my target vanished" mysteries.&lt;/p&gt;

&lt;p&gt;When the regex matches and the action writes a label, &lt;code&gt;replacement&lt;/code&gt; supplies the value. Capture groups expand as &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;${1}&lt;/code&gt;, or named groups &lt;code&gt;$name&lt;/code&gt;/&lt;code&gt;${name}&lt;/code&gt;; the default replacement is &lt;code&gt;$1&lt;/code&gt;, which is why a bare &lt;code&gt;replace&lt;/code&gt; with &lt;code&gt;regex: (.*)&lt;/code&gt; copies the source value through unchanged. &lt;code&gt;modulus&lt;/code&gt; is only read by &lt;code&gt;hashmod&lt;/code&gt;, and &lt;code&gt;target_label&lt;/code&gt; is required by &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;hashmod&lt;/code&gt;, &lt;code&gt;lowercase&lt;/code&gt;, &lt;code&gt;uppercase&lt;/code&gt;, &lt;code&gt;keepequal&lt;/code&gt;, and &lt;code&gt;dropequal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6j92zfsax2cz9pwkil16.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6j92zfsax2cz9pwkil16.webp" alt="Synthwave illustration of a relabel rule: source_labels flow into an anchored regex, the $1:$2 replacement expands, and actions like replace, keep, labelmap and hashmod rewrite the labels." width="799" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The actions one by one: replace, keep, drop, labelmap, labelkeep, labeldrop, hashmod
&lt;/h2&gt;

&lt;p&gt;Prometheus supports eleven actions. Each example below is a complete, runnable rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  replace
&lt;/h3&gt;

&lt;p&gt;Join the source labels, match the regex, expand &lt;code&gt;$1&lt;/code&gt;/&lt;code&gt;${1}&lt;/code&gt; in &lt;code&gt;replacement&lt;/code&gt;, and set &lt;code&gt;target_label&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__address__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([^:]+):.*"&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ip&lt;/span&gt;
  &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;__address__="10.0.0.5:8080"&lt;/code&gt; becomes a new label &lt;code&gt;ip="10.0.0.5"&lt;/code&gt;. If the regex doesn't match, the label set is left unchanged. There's a sharp edge worth memorizing: &lt;strong&gt;if the expanded replacement is the empty string, &lt;code&gt;replace&lt;/code&gt; deletes the target label&lt;/strong&gt; instead of setting it to blank.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# When tmp_instance is empty, this DELETES the instance label.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;tmp_instance&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(.+)"&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;instance&lt;/span&gt;
  &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On &lt;code&gt;instance="old"&lt;/code&gt;, &lt;code&gt;tmp_instance=""&lt;/code&gt;, the regex &lt;code&gt;(.+)&lt;/code&gt; fails to match an empty value, so nothing happens — &lt;code&gt;instance&lt;/code&gt; survives. But change the source so the expansion lands on an empty string and the &lt;code&gt;instance&lt;/code&gt; label disappears entirely. That asymmetry is a frequent source of "where did my label go?"&lt;/p&gt;

&lt;h3&gt;
  
  
  keep
&lt;/h3&gt;

&lt;p&gt;Drop the whole target unless the joined source matches the regex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__meta_kubernetes_pod_annotation_prometheus_io_scrape&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only pods annotated &lt;code&gt;prometheus.io/scrape: "true"&lt;/code&gt; survive; everything else is dropped before scraping. &lt;code&gt;keep&lt;/code&gt; is an allow-list gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  drop
&lt;/h3&gt;

&lt;p&gt;The mirror of &lt;code&gt;keep&lt;/code&gt;: drop the target when the joined source &lt;em&gt;does&lt;/em&gt; match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__name__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;drop&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;go_gc_.*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Used in &lt;code&gt;metric_relabel_configs&lt;/code&gt;, this silences the entire &lt;code&gt;go_gc_*&lt;/code&gt; metric family before it's stored. &lt;code&gt;drop&lt;/code&gt; is a deny-list gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  labelmap
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;labelmap&lt;/code&gt; operates on label &lt;strong&gt;names&lt;/strong&gt;, not values. For every label whose name matches the regex, it sets a new label — named by the expanded replacement — to that label's value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;labelmap&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__meta_kubernetes_pod_label_(.+)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A label &lt;code&gt;__meta_kubernetes_pod_label_app="api"&lt;/code&gt; produces a new label &lt;code&gt;app="api"&lt;/code&gt;. This is the canonical move for promoting Kubernetes pod labels to plain labels. The default &lt;code&gt;replacement&lt;/code&gt; of &lt;code&gt;$1&lt;/code&gt; is what writes the captured suffix as the new name.&lt;/p&gt;

&lt;h3&gt;
  
  
  labelkeep / labeldrop
&lt;/h3&gt;

&lt;p&gt;Both filter labels by name. &lt;code&gt;labeldrop&lt;/code&gt; removes every label whose name matches; &lt;code&gt;labelkeep&lt;/code&gt; removes every label whose name does &lt;em&gt;not&lt;/em&gt; match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Strip all leftover service-discovery metadata.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;labeldrop&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__meta_.+"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Keep only the four labels you care about; drop everything else.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;labelkeep&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(__name__|job|instance|severity)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  hashmod
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;hashmod&lt;/code&gt; sets &lt;code&gt;target_label&lt;/code&gt; to a stable shard number. It takes the MD5 of the joined source, reads the last 8 bytes of that digest as a big-endian 64-bit integer, and stores &lt;code&gt;hash % modulus&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__address__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashmod&lt;/span&gt;
  &lt;span class="na"&gt;modulus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__tmp_shard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every target gets a deterministic &lt;code&gt;__tmp_shard&lt;/code&gt; value of &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, or &lt;code&gt;2&lt;/code&gt;. The MD5 recipe matters: the Relabel Tester reproduces this exact byte-for-byte, so the shard values it shows are the values Prometheus will compute.&lt;/p&gt;

&lt;h3&gt;
  
  
  keepequal / dropequal
&lt;/h3&gt;

&lt;p&gt;These two take no regex. They compare the joined source value to the &lt;em&gt;current value&lt;/em&gt; of &lt;code&gt;target_label&lt;/code&gt; and keep or drop on equality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Drop the target if its port already equals the discovered one.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__meta_port&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dropequal&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;keepequal&lt;/code&gt; keeps only when the two are equal; &lt;code&gt;dropequal&lt;/code&gt; drops when they're equal.&lt;/p&gt;

&lt;h3&gt;
  
  
  lowercase / uppercase
&lt;/h3&gt;

&lt;p&gt;Set &lt;code&gt;target_label&lt;/code&gt; to the lower- or upper-cased joined source value — handy for normalizing case-inconsistent discovery labels.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lowercase&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;environment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;environment="PRODUCTION"&lt;/code&gt; becomes &lt;code&gt;environment="production"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  _&lt;em&gt;meta&lt;/em&gt; labels from service discovery and why they matter
&lt;/h2&gt;

&lt;p&gt;Every service discovery mechanism — Kubernetes, EC2, Consul, file-based — attaches &lt;code&gt;__meta_*&lt;/code&gt; labels to each target it finds. These are &lt;em&gt;only&lt;/em&gt; available during &lt;code&gt;relabel_configs&lt;/code&gt;. They are stripped before the scrape, so if you want any of that metadata to survive as a real label, you have to copy it out with &lt;code&gt;replace&lt;/code&gt; or &lt;code&gt;labelmap&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fprometheus-relabel-configs-explained-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fprometheus-relabel-configs-explained-diagram.svg" alt="The relabel pipeline for one rule: input labels, join the source_labels with the separator, match the regex, apply the action, and produce the output labels." width="1200" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Kubernetes pod target arrives looking roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;__address__="10.0.0.5:8080"
__meta_kubernetes_namespace="default"
__meta_kubernetes_pod_name="api-7d9f"
__meta_kubernetes_pod_label_app="api"
__meta_kubernetes_pod_annotation_prometheus_io_scrape="true"
__meta_kubernetes_pod_annotation_prometheus_io_port="9100"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;__meta_*&lt;/code&gt; labels are why relabeling exists at all. They carry the discovery context — which namespace, which annotations, which pod labels — that you turn into scrape decisions (&lt;code&gt;keep&lt;/code&gt; on the scrape annotation) and into durable labels (&lt;code&gt;labelmap&lt;/code&gt; the pod labels). Anything starting with a double underscore is internal and dropped after relabeling, with &lt;code&gt;__name__&lt;/code&gt; (the metric name) being the notable one that survives into storage. Because these labels only exist at relabel time, the only safe way to confirm a rule reads them correctly is to feed a realistic &lt;code&gt;__meta_*&lt;/code&gt; set through your rules and look at the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipes you will reuse
&lt;/h2&gt;

&lt;p&gt;These are the patterns that show up in almost every real scrape config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep only prod targets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__meta_kubernetes_namespace&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod|production"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anchored, so &lt;code&gt;prod&lt;/code&gt; matches the namespace &lt;code&gt;prod&lt;/code&gt; exactly and &lt;code&gt;staging-prod&lt;/code&gt; would &lt;em&gt;not&lt;/em&gt; match unless you write &lt;code&gt;.*prod.*&lt;/code&gt;. The &lt;code&gt;|&lt;/code&gt; alternation keeps both naming conventions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drop noisy metrics (metric_relabel_configs)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;metric_relabel_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__name__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;drop&lt;/span&gt;
    &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;go_gc_.*|process_.*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runs after the scrape, dropping high-cardinality families before they hit storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  hashmod sharding
&lt;/h3&gt;

&lt;p&gt;The two-rule horizontal sharding pattern — hash into a temp label, then keep only the shard this Prometheus owns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__address__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashmod&lt;/span&gt;
  &lt;span class="na"&gt;modulus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__tmp_shard&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__tmp_shard&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this against four sample addresses in the tester and you'll see exactly which two or three land on shard &lt;code&gt;0&lt;/code&gt; and survive — the others are dropped, marked with the rule and action responsible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Map SD labels with labelmap, then rewrite the address
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Promote every pod label to a plain label.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;labelmap&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__meta_kubernetes_pod_label_(.+)"&lt;/span&gt;

&lt;span class="c1"&gt;# Rebuild __address__ from the IP and an annotated port.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__address__&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;__meta_kubernetes_pod_annotation_prometheus_io_port&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replace&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([^:]+)(?::&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;d+)?;(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;d+)"&lt;/span&gt;
  &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$1:$2"&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__address__&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second rule shows the joined-source idiom in action: two &lt;code&gt;source_labels&lt;/code&gt; joined by the default &lt;code&gt;;&lt;/code&gt; separator, with a regex written to account for that separator. &lt;code&gt;__address__="10.0.2.4:8080"&lt;/code&gt; joined with port &lt;code&gt;9100&lt;/code&gt; becomes &lt;code&gt;10.0.2.4:8080;9100&lt;/code&gt;, the regex captures &lt;code&gt;10.0.2.4&lt;/code&gt; and &lt;code&gt;9100&lt;/code&gt;, and the address is rebuilt as &lt;code&gt;10.0.2.4:9100&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test before you ship
&lt;/h2&gt;

&lt;p&gt;Relabeling is the one part of a Prometheus config where being almost right produces no error and no warning — just missing or wrong series. The regex anchoring, the empty-replacement deletion, the MD5 hashmod, the join order of multiple &lt;code&gt;source_labels&lt;/code&gt;: each is easy to get subtly wrong, and a live Prometheus won't tell you which one bit you.&lt;/p&gt;

&lt;p&gt;Paste the recipes from this post, with a realistic set of &lt;code&gt;__meta_*&lt;/code&gt; labels, into the &lt;a href="https://opscanopy.com/prometheus-relabel-tester/" rel="noopener noreferrer"&gt;Prometheus Relabel Tester&lt;/a&gt; and you'll see the joined value, the matched (or unmatched) regex, the per-label diff, and a clear flag — naming the rule and action — whenever a target is dropped. It runs entirely in your browser, so you can safely paste internal scrape configs.&lt;/p&gt;

&lt;p&gt;Once the labels are shaped the way you want, the next questions are what you query and how you alert. Break down an expression with &lt;a href="https://opscanopy.com/promql-explainer/" rel="noopener noreferrer"&gt;the PromQL Explainer&lt;/a&gt;, or if you're moving rules between Loki and Prometheus, translate them with &lt;a href="https://opscanopy.com/logql-promql-helper/" rel="noopener noreferrer"&gt;the LogQL ↔ PromQL Helper&lt;/a&gt;. Get the labels right first — everything downstream depends on them.&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>observability</category>
      <category>relabeling</category>
    </item>
    <item>
      <title>Learn DevOps in 90 Days: the free, incident-first path from developer to DevOps engineer</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Sun, 09 Aug 2026 05:50:10 +0000</pubDate>
      <link>https://dev.to/opscanopy/learn-devops-in-90-days-the-free-incident-first-path-from-developer-to-devops-engineer-oc7</link>
      <guid>https://dev.to/opscanopy/learn-devops-in-90-days-the-free-incident-first-path-from-developer-to-devops-engineer-oc7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Flearn-devops-in-90-days-hero.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Flearn-devops-in-90-days-hero.svg" alt="Learn DevOps in 90 days: a free, incident-first path from developer to DevOps engineer" width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a moment every new ops engineer remembers: the first time something real breaks. The tutorial ended weeks ago, the happy path is nowhere in sight, and a pod is stuck in &lt;code&gt;CrashLoopBackOff&lt;/code&gt; — or a cron job silently stopped, or the whole site answers 502. Tutorials teach commands. Incidents teach engineers.&lt;/p&gt;

&lt;p&gt;That gap is why we built &lt;a href="https://opscanopy.com/mission-90/" rel="noopener noreferrer"&gt;Mission: 90 Days DevOps&lt;/a&gt; — a free, guided program that takes you from your first terminal command to job-ready in 90 days, one focused lesson a day, with a story-driven incident mission to fix at the end of every week. All 90 days and all 10 missions are live now. No signup, no email wall, no paywall; your progress saves in your own browser. This post explains what's in it, the order and why, and the design decisions behind it — including the ones with real tradeoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ordering rule: every layer must let you debug the layer above it
&lt;/h2&gt;

&lt;p&gt;Most DevOps roadmaps fail in one of two ways. Either they hand you a 200-node skill tree with no sequence, or they start with the shiny thing — Kubernetes before Linux, which is how people end up copy-pasting &lt;code&gt;kubectl&lt;/code&gt; commands they can't debug.&lt;/p&gt;

&lt;p&gt;The program's order comes from one rule: &lt;strong&gt;every layer must let you debug the layer above it.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes problems are usually container problems.&lt;/li&gt;
&lt;li&gt;Container problems are usually Linux problems.&lt;/li&gt;
&lt;li&gt;Cloud networking problems are usually DNS-and-ports problems you can learn on localhost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the 90 days run Linux first, containers second, cloud third, orchestration fourth, and job preparation last — 45 to 60 minutes a day, roughly 80 hours of core work in total. That is a real commitment, not a "10 minutes a day" promise, but it fits alongside a full-time job, and 90 days is short enough that you can see the finish line from Day 1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Flearn-devops-in-90-days-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Flearn-devops-in-90-days-diagram.svg" alt="The 90-day path: five phases from Linux fundamentals to job-ready, with an incident mission capping every week" width="1200" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The five phases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 — Linux &amp;amp; the terminal (Days 1–21).&lt;/strong&gt; Three weeks on files, permissions, processes, systemd, logs, DNS and ports, bash, cron and SSH. It feels long when Kubernetes is calling, but nearly every incident ends with someone SSH'd into a box reading logs — this is where that stops being scary. Everything runs locally on WSL2 or any Linux machine. Cost: zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2 — Docker &amp;amp; CI/CD (Days 22–45).&lt;/strong&gt; Packaging software and shipping it automatically, deliberately paired: images, Dockerfiles, volumes, networks and compose on one side; real Git workflows and GitHub Actions pipelines on the other. The phase ends with &lt;strong&gt;Project 1&lt;/strong&gt;: containerizing &lt;code&gt;linkstash&lt;/code&gt;, a FastAPI URL shortener, database included. Still fully local, still zero cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3 — AWS (Days 46–65).&lt;/strong&gt; IAM and VPC first — because most AWS confusion is "why can't this thing talk to that thing", and the answer is almost always a security group or a subnet route — then S3, RDS, load balancers and monitoring. &lt;strong&gt;Project 2&lt;/strong&gt; deploys the same &lt;code&gt;linkstash&lt;/code&gt; container properly: ECS Fargate behind an ALB with RDS Postgres across two availability zones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 4 — Kubernetes &amp;amp; Terraform (Days 66–85).&lt;/strong&gt; Pods, Services, config, probes, ingress and Helm — practiced on local &lt;code&gt;kind&lt;/code&gt; clusters first, so mistakes are free — then Terraform, so infrastructure becomes reviewable code instead of console clicks. &lt;strong&gt;Project 3&lt;/strong&gt; is the capstone: &lt;code&gt;linkstash&lt;/code&gt; again, now on k3s on a single EC2 instance, provisioned entirely by Terraform, packaged as a Helm chart, served over real TLS by Traefik and cert-manager.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 5 — Job-Ready (Days 86–90).&lt;/strong&gt; The part every roadmap skips: turning the work into a hire. A resume rebuilt around the three projects, portfolio and GitHub polish, incident-management fundamentals — severity levels, blameless postmortems, error budgets — and an interview drill. Every day of the program ends with three to five interview questions, so by Day 86 you have already answered more than 300.&lt;/p&gt;

&lt;h2&gt;
  
  
  One app, carried up the whole stack
&lt;/h2&gt;

&lt;p&gt;The design decision we would defend hardest: the program builds &lt;strong&gt;one application three ways&lt;/strong&gt;, not three throwaway projects.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;linkstash&lt;/code&gt; is containerized in Phase 2, deployed to AWS in Phase 3, and orchestrated on Kubernetes in Phase 4. In an interview, that turns "I did some tutorials" into "here is one system I have run three ways, and here is why each layer exists." The compare-and-contrast — compose file versus task definition versus Helm chart, &lt;code&gt;depends_on&lt;/code&gt; versus target groups versus readiness probes — is precisely the story interviewers ask for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Incidents are the curriculum, not a bonus
&lt;/h2&gt;

&lt;p&gt;Reading about &lt;code&gt;journalctl&lt;/code&gt; and using it at 00:14 during an outage are different skills, and only one of them is a job. So the week caps are not quizzes — they are &lt;strong&gt;missions&lt;/strong&gt;: story-driven incident simulations that run in a terminal in your browser. No setup, no cloud account, no code required.&lt;/p&gt;

&lt;p&gt;You start week one with &lt;em&gt;Server Down!&lt;/em&gt; — a production web server is down at 2 a.m. and you have to find the runaway process, kill it, and bring nginx back. By the final week you face &lt;em&gt;The Midnight Outage&lt;/em&gt;: a SEV-1 where a single security-group change cascades into failed load-balancer health checks, a DNS failover, and pods in the failover region that cannot start because someone deleted the Secret they mount. The fix has to land &lt;strong&gt;upstream-first&lt;/strong&gt; — reopen the network path, restore the Secret, then confirm DNS healed — in that order, because restarting pods was never the problem.&lt;/p&gt;

&lt;p&gt;In between: a DNS mystery, a permissions lockout, a crash-looping container stack, a broken CI pipeline, a surprise AWS bill, a dropped production table, a Kubernetes cluster in chaos, and a stale Terraform state lock. Ten missions, each rehearsing the exact diagnostic loop its week taught.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designed to cost (almost) nothing
&lt;/h2&gt;

&lt;p&gt;A learning path that quietly runs up a cloud bill is a broken learning path. So the budget rules are structural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phases 1, 2 and the Kubernetes fundamentals run &lt;strong&gt;entirely locally&lt;/strong&gt; — WSL2, Docker, &lt;code&gt;kind&lt;/code&gt;, and local Terraform providers. Zero cloud spend.&lt;/li&gt;
&lt;li&gt;Every AWS day opens with a &lt;strong&gt;cost box&lt;/strong&gt; stating exactly what the lab costs and under what conditions it is free, and ends with a &lt;strong&gt;mandatory teardown&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The Kubernetes capstone uses k3s on one t3.small (about $19/month if you forget it, effectively free on the free tier with same-day teardown) instead of EKS, whose control plane alone costs about $73/month. Same Kubernetes API, same manifests, same Helm — you just don't pay AWS to hold the API server. The tradeoff, stated honestly: you won't touch EKS-specific glue like IRSA or managed node groups, which is fine to learn on the job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a day looks like
&lt;/h2&gt;

&lt;p&gt;Every one of the 90 days has the same shape, so you always know what "done" means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A short concept&lt;/strong&gt; — under 600 words, one diagram, one real-world analogy. Read it in five minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A hands-on lab&lt;/strong&gt; — real commands with real output, reproducible on your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Common errors &amp;amp; fixes&lt;/strong&gt; — the actual error strings you will hit, why they happen, and how you'd spot them in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interview Q&amp;amp;A&lt;/strong&gt; — three to five questions with answers worth saying out loud.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Optional "Go Deeper" extras exist for the days you have more time, and are never assumed by the next lesson.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest tradeoffs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;90 days makes you hireable-junior, not senior.&lt;/strong&gt; You'll contribute and debug from week one on the job; depth comes with pager time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's opinionated.&lt;/strong&gt; GitHub Actions rather than Jenkins, AWS rather than Azure, k3s rather than EKS. Each slot got the highest-leverage default; your target job may differ.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-paced means self-discipline.&lt;/strong&gt; Progress tracking helps; it doesn't replace showing up daily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The AWS phase needs a card on file.&lt;/strong&gt; The teardown discipline keeps spend near zero, but the risk is never exactly zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start today
&lt;/h2&gt;

&lt;p&gt;Everything is live: &lt;a href="https://opscanopy.com/mission-90/" rel="noopener noreferrer"&gt;all 90 days&lt;/a&gt;, the &lt;a href="https://opscanopy.com/mission-90/missions/" rel="noopener noreferrer"&gt;ten missions&lt;/a&gt;, and the three projects. Start with Day 1 — or, if you want to feel the point of the whole program in ten minutes, play &lt;em&gt;Server Down!&lt;/em&gt; first. It needs nothing but a browser tab, and it ends the way every good incident does: with the site back up, and you knowing exactly why.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>How Alertmanager Routing Works: Matchers, continue, and the Route Tree</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Sun, 02 Aug 2026 13:06:52 +0000</pubDate>
      <link>https://dev.to/opscanopy/how-alertmanager-routing-works-matchers-continue-and-the-route-tree-5bnj</link>
      <guid>https://dev.to/opscanopy/how-alertmanager-routing-works-matchers-continue-and-the-route-tree-5bnj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fhow-alertmanager-routing-works-hero.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fhow-alertmanager-routing-works-hero.svg" alt="Diagram of Alertmanager routing: an alert's labels enter the route tree at the root and flow down matched child routes to a receiver" width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;severity=critical&lt;/code&gt; alert fired last night and the on-call team never got paged. The alert was real, the receiver existed, the Slack webhook worked. The problem was three lines higher in the config: a broad catch-all route sat above the team route and quietly swallowed everything that reached it. Nobody touched the receiver — they touched the order.&lt;/p&gt;

&lt;p&gt;That is what makes Alertmanager routing easy to get wrong. The receivers are usually fine. The route tree is where the surprises live. Once you have a precise model of how the route tree is walked — how matchers are evaluated, when &lt;code&gt;continue&lt;/code&gt; keeps an alert moving, and what each child inherits from its parent — "why did this alert go there?" stops being a guessing game. This post builds that model, and every rule here matches what the &lt;a href="https://opscanopy.com/alertmanager-route-tester/" rel="noopener noreferrer"&gt;Alertmanager Route Tester&lt;/a&gt; actually does when it walks a tree against a sample alert.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing is a tree, not a list
&lt;/h2&gt;

&lt;p&gt;The most common misread of an Alertmanager config is treating &lt;code&gt;routes:&lt;/code&gt; as a flat list of rules that each alert is checked against. It is not a list. It is a tree, and every alert enters at the same place: the root route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;        &lt;span class="c1"&gt;# the root — the catch-all&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cluster'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;group_wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
  &lt;span class="na"&gt;group_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
  &lt;span class="na"&gt;repeat_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4h&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                              &lt;span class="c1"&gt;# child routes&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-X-mails'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-DB-pages'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-Y-mails'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root route is special: it is entered for &lt;strong&gt;every&lt;/strong&gt; alert, regardless of its own matchers. It is the catch-all. Its &lt;code&gt;receiver&lt;/code&gt; is the default an alert lands on when nothing more specific matches, and its grouping fields are the baseline that everything below inherits. Inside the root sits a &lt;code&gt;routes:&lt;/code&gt; list — its children. Each child can have its own &lt;code&gt;routes:&lt;/code&gt;, and so on down. An alert descends from the root through whichever children match, and the receiver it ends up on is the one at the node where the descent stops.&lt;/p&gt;

&lt;p&gt;So when you read an &lt;code&gt;alertmanager.yml&lt;/code&gt;, do not scan the route list looking for the rule that matches. Start at the root and walk down. The Alertmanager route tree is a decision tree you trace top-to-bottom, depth-first.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a route matches: matchers syntax (and the older match/match_re)
&lt;/h2&gt;

&lt;p&gt;A route node matches an alert when &lt;strong&gt;all&lt;/strong&gt; of its own matchers hold against the alert's labels. Logical AND, no exceptions. A node with no matchers always matches. There are three ways to declare those Alertmanager matchers, and you will see all three in real configs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Modern matchers: syntax — preferred. One operator per line.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;staging-slack'&lt;/span&gt;
    &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;env=~"staging-.*"&lt;/span&gt;      &lt;span class="c1"&gt;# =~ regex&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity!="info"&lt;/span&gt;       &lt;span class="c1"&gt;# != inequality&lt;/span&gt;

  &lt;span class="c1"&gt;# Older match: exact string equality on each key.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-X-mails'&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;

  &lt;span class="c1"&gt;# Older match_re: each value is a regex.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prod-pager'&lt;/span&gt;
    &lt;span class="na"&gt;match_re&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prod-.*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The modern &lt;code&gt;matchers:&lt;/code&gt; form carries its operator inline. There are four: &lt;code&gt;=&lt;/code&gt; (equals), &lt;code&gt;!=&lt;/code&gt; (not equals), &lt;code&gt;=~&lt;/code&gt; (regex match), and &lt;code&gt;!~&lt;/code&gt; (regex no-match). Values may be quoted or bare. The two older forms are sugar over the same engine — &lt;code&gt;match:&lt;/code&gt; is a set of &lt;code&gt;=&lt;/code&gt; matchers, and &lt;code&gt;match_re:&lt;/code&gt; is a set of &lt;code&gt;=~&lt;/code&gt; matchers.&lt;/p&gt;

&lt;p&gt;Two details trip people up constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regexes are fully anchored.&lt;/strong&gt; Alertmanager wraps every &lt;code&gt;=~&lt;/code&gt;, &lt;code&gt;!~&lt;/code&gt;, and &lt;code&gt;match_re&lt;/code&gt; pattern as &lt;code&gt;^(?:…)$&lt;/code&gt;. So &lt;code&gt;env=~"staging"&lt;/code&gt; matches the value &lt;code&gt;staging&lt;/code&gt; and nothing else — &lt;code&gt;env=staging-eu&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; match. You have to write &lt;code&gt;env=~"staging-.*"&lt;/code&gt; to cover the rest of the value. This is the single most frequent cause of "my route matches nothing."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A missing label is the empty string.&lt;/strong&gt; Alertmanager compares an absent label as &lt;code&gt;""&lt;/code&gt;. So &lt;code&gt;foo=""&lt;/code&gt; matches an alert that has no &lt;code&gt;foo&lt;/code&gt; label at all, and &lt;code&gt;foo!=""&lt;/code&gt; requires &lt;code&gt;foo&lt;/code&gt; to be present and non-empty. Useful, and occasionally surprising.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting those labels onto the alert in the first place is a separate job that happens at scrape time — if the label your matcher checks was never set, walk it back to your scrape config with the &lt;a href="https://opscanopy.com/prometheus-relabel-tester/" rel="noopener noreferrer"&gt;Prometheus Relabel Tester&lt;/a&gt; before you blame the route tree.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu12klibobvaon0l8tn31.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu12klibobvaon0l8tn31.webp" alt="Illustration: an incoming alert descends the Alertmanager route tree from the root route into child routes with matchers and continue: true, ending on the matched route" width="799" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Depth-first matching and continue: first matching sibling wins unless continue is true
&lt;/h2&gt;

&lt;p&gt;Here is the rule that the late-night example broke. Within a matched route, child routes are evaluated &lt;strong&gt;in order, top to bottom&lt;/strong&gt;. The alert descends into the &lt;strong&gt;first&lt;/strong&gt; child whose matchers all hold — and then, by default, the sibling scan &lt;strong&gt;stops&lt;/strong&gt;. Later siblings are never even checked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# TRAP: the broad rule above shadows the specific one&lt;/span&gt;
&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;catch-all&lt;/span&gt;
    &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity=~".*"'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# matches everything&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-pager&lt;/span&gt;             &lt;span class="c1"&gt;# NEVER reached&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;database&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;service=database, severity=critical&lt;/code&gt; alert hits &lt;code&gt;catch-all&lt;/code&gt; first, that match stops the scan, and &lt;code&gt;db-pager&lt;/code&gt; is dead code. The fix is either to order specific-before-broad, or to set &lt;code&gt;continue: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;continue: true&lt;/code&gt; on a matched route tells Alertmanager &lt;strong&gt;not&lt;/strong&gt; to stop the sibling scan after that route matches. Evaluation keeps going to the later siblings, each of which may also match. That is the only way a single alert can land on more than one receiver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Mirror every critical alert to an audit receiver,&lt;/span&gt;
&lt;span class="c1"&gt;# THEN keep routing so the owning team is still paged.&lt;/span&gt;
&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all-critical-audit&lt;/span&gt;
    &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity="critical"'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;continue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;               &lt;span class="c1"&gt;# &amp;lt;- do not stop here&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;team-backend&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;backend&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a &lt;code&gt;team=backend, severity=critical&lt;/code&gt; alert, the first route matches and would normally stop the scan — but &lt;code&gt;continue: true&lt;/code&gt; keeps it alive, the second route also matches, and &lt;strong&gt;both&lt;/strong&gt; receivers fire. Drop the &lt;code&gt;continue&lt;/code&gt; and only &lt;code&gt;all-critical-audit&lt;/code&gt; fires; the team never hears about it.&lt;/p&gt;

&lt;p&gt;The walk is depth-first: when a child matches, the alert descends into &lt;em&gt;that child's&lt;/em&gt; subtree and resolves there before any &lt;code&gt;continue&lt;/code&gt; carries it to the next sibling. The Alertmanager Route Tester tags each receiver that was reached only because an earlier sibling set &lt;code&gt;continue: true&lt;/code&gt;, so you can see at a glance which matches are the primary path and which are fan-out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grouping: group_by, group_wait, group_interval, repeat_interval
&lt;/h2&gt;

&lt;p&gt;Routing decides &lt;em&gt;where&lt;/em&gt; an alert goes. Grouping decides &lt;em&gt;how&lt;/em&gt; its notifications are batched and paced once it gets there. Four fields control it, and they live on route nodes right alongside the matchers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cluster'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;group_wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;          &lt;span class="c1"&gt;# wait this long to collect more alerts for a new group&lt;/span&gt;
  &lt;span class="na"&gt;group_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;       &lt;span class="c1"&gt;# then wait this long before sending updates to that group&lt;/span&gt;
  &lt;span class="na"&gt;repeat_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4h&lt;/span&gt;      &lt;span class="c1"&gt;# re-send an unresolved group no more often than this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;group_by&lt;/code&gt;&lt;/strong&gt; is the list of labels that defines a group. Alerts that share the same values for those labels are bundled into one notification. A common special case is &lt;code&gt;group_by: ['...']&lt;/code&gt;, which groups by &lt;em&gt;all&lt;/em&gt; labels (every distinct alert is its own group), and the absence of grouping aggregates everything into a single group.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;group_wait&lt;/code&gt;&lt;/strong&gt; is how long Alertmanager holds a brand-new group before sending the first notification, so a burst of related alerts arrives as one page instead of twenty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;group_interval&lt;/code&gt;&lt;/strong&gt; is the minimum gap before it sends an &lt;em&gt;updated&lt;/em&gt; notification for a group that already fired (e.g. when a new alert joins the group).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;repeat_interval&lt;/code&gt;&lt;/strong&gt; is how often it re-notifies about a group that is still firing and unresolved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the difference between one useful page and an alert storm. And critically — they are inherited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inheritance: child routes inherit receiver and group_by from the parent
&lt;/h2&gt;

&lt;p&gt;A child route does not have to repeat the receiver and grouping it wants. Anything it does &lt;strong&gt;not&lt;/strong&gt; set is inherited from the nearest ancestor that did. This is per-field: a child can override &lt;code&gt;group_by&lt;/code&gt; while still inheriting &lt;code&gt;group_wait&lt;/code&gt;, &lt;code&gt;group_interval&lt;/code&gt;, &lt;code&gt;repeat_interval&lt;/code&gt;, and even &lt;code&gt;receiver&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cluster'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;group_wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
  &lt;span class="na"&gt;repeat_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4h&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-DB-pages'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database&lt;/span&gt;
      &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cluster'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;database'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="c1"&gt;# group_wait and repeat_interval are INHERITED from the root:&lt;/span&gt;
      &lt;span class="c1"&gt;#   group_wait: 30s, repeat_interval: 4h&lt;/span&gt;
      &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;critical&lt;/span&gt;
          &lt;span class="c1"&gt;# No receiver set here, so it INHERITS 'team-DB-pages'.&lt;/span&gt;
          &lt;span class="c1"&gt;# No group_by set, so it INHERITS [alertname, cluster, database].&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fhow-alertmanager-routing-works-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fhow-alertmanager-routing-works-diagram.svg" alt="An Alertmanager route tree with a root route branching into child routes labelled by matchers, leaves are receivers, and a sample alert flows down the matched path which is highlighted, with one branch marked continue true" width="1200" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The deepest node in that tree sets neither a receiver nor &lt;code&gt;group_by&lt;/code&gt;, yet a &lt;code&gt;service=database, severity=critical&lt;/code&gt; alert that reaches it pages &lt;code&gt;team-DB-pages&lt;/code&gt; and groups by &lt;code&gt;[alertname, cluster, database]&lt;/code&gt; — both pulled down the chain. This is why the leaf you are staring at may not tell the whole story: the effective receiver and grouping are assembled by walking &lt;em&gt;up&lt;/em&gt; from the matched node to the first ancestor that set each field. When you debug a misrouted or mis-grouped alert, resolve the inheritance, not just the leaf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading a real route tree: where a given alert lands
&lt;/h2&gt;

&lt;p&gt;Put it together. Here is a complete tree with three children at the top level and a nested subtree under one of them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;group_wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
  &lt;span class="na"&gt;repeat_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4h&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;all-critical-audit'&lt;/span&gt;
      &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity="critical"&lt;/span&gt;
      &lt;span class="na"&gt;continue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;                 &lt;span class="c1"&gt;# mirror, then keep going&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;web-team'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
      &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;instance'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;web-team-pager'&lt;/span&gt;
          &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity="critical"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;web-team-slack'&lt;/span&gt;
          &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity=~"warning|info"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-Y-mails'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now trace an alert with these labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;alertname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Latency
&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web
&lt;span class="nv"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;critical
&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web-3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walking the tree, depth-first, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Root&lt;/strong&gt; is entered (always). It does not stop here; it has children to evaluate.&lt;/li&gt;
&lt;li&gt;First child, &lt;code&gt;all-critical-audit&lt;/code&gt;: &lt;code&gt;severity="critical"&lt;/code&gt; holds. It matches → &lt;code&gt;all-critical-audit&lt;/code&gt; fires. It has &lt;code&gt;continue: true&lt;/code&gt;, so the scan does &lt;strong&gt;not&lt;/strong&gt; stop.&lt;/li&gt;
&lt;li&gt;Second child, &lt;code&gt;web-team&lt;/code&gt;: &lt;code&gt;service: web&lt;/code&gt; holds. The alert descends into its subtree.

&lt;ul&gt;
&lt;li&gt;First grandchild, &lt;code&gt;web-team-pager&lt;/code&gt;: &lt;code&gt;severity="critical"&lt;/code&gt; holds → &lt;code&gt;web-team-pager&lt;/code&gt; fires. No &lt;code&gt;continue&lt;/code&gt;, so this branch stops here. Effective &lt;code&gt;group_by&lt;/code&gt; is &lt;code&gt;[alertname, instance]&lt;/code&gt;, inherited from &lt;code&gt;web-team&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;web-team&lt;/code&gt; match (a non-&lt;code&gt;continue&lt;/code&gt; match) stops the top-level scan, so &lt;code&gt;team-Y-mails&lt;/code&gt; is never evaluated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Final result: the alert reaches &lt;strong&gt;two&lt;/strong&gt; receivers — &lt;code&gt;all-critical-audit&lt;/code&gt; (via &lt;code&gt;continue&lt;/code&gt;) and &lt;code&gt;web-team-pager&lt;/code&gt; (the primary path). Flip &lt;code&gt;severity&lt;/code&gt; to &lt;code&gt;warning&lt;/code&gt; and the picture changes: &lt;code&gt;all-critical-audit&lt;/code&gt; drops out, and inside &lt;code&gt;web-team&lt;/code&gt; the alert falls to &lt;code&gt;web-team-slack&lt;/code&gt; instead. Remove &lt;code&gt;service=web&lt;/code&gt; and it never enters that subtree at all, falling through to &lt;code&gt;team-Y-mails&lt;/code&gt; if &lt;code&gt;team=backend&lt;/code&gt;, or to the root's &lt;code&gt;default-receiver&lt;/code&gt; if nothing matches.&lt;/p&gt;

&lt;p&gt;If your alert rules themselves aren't firing the way you expect — wrong labels, wrong severity, wrong timing — that is upstream of routing entirely; prove the rule first with &lt;a href="https://opscanopy.com/loki-alert-rule-tester/" rel="noopener noreferrer"&gt;AlertLint&lt;/a&gt;, then trace where its output lands here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test your tree
&lt;/h2&gt;

&lt;p&gt;You can do this walk by hand, and for a three-node tree it is worth doing once to internalize the model. But real trees nest five levels deep, mix &lt;code&gt;match&lt;/code&gt;, &lt;code&gt;match_re&lt;/code&gt;, and &lt;code&gt;matchers&lt;/code&gt;, and sprinkle &lt;code&gt;continue&lt;/code&gt; across siblings — and the cost of getting it wrong is a SEV-1 that pages nobody, or a routine warning that wakes the whole team.&lt;/p&gt;

&lt;p&gt;So make it cheap to check. Paste your route tree and a sample alert's labels into the &lt;a href="https://opscanopy.com/alertmanager-route-tester/" rel="noopener noreferrer"&gt;Alertmanager Route Tester&lt;/a&gt; and it does exactly the walk above — entirely in your browser, nothing uploaded. It reports every receiver the alert reaches in evaluation order, the route-path breadcrumb from root to each matched node, a tag on any receiver reached only via &lt;code&gt;continue: true&lt;/code&gt;, and the effective &lt;code&gt;group_by&lt;/code&gt; after inheritance. It reproduces the semantics this post describes: anchored regexes, missing-label-as-empty-string, first-match-then-&lt;code&gt;continue&lt;/code&gt;, and per-field inheritance.&lt;/p&gt;

&lt;p&gt;The next time an alert lands somewhere unexpected, you do not have to fire a real one and watch. Paste the tree, paste the labels, and read off the path it actually took.&lt;/p&gt;

</description>
      <category>alertmanager</category>
      <category>observability</category>
      <category>alerting</category>
    </item>
    <item>
      <title>docker run vs Docker Compose: A Practical Migration Guide</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Sun, 02 Aug 2026 13:06:17 +0000</pubDate>
      <link>https://dev.to/opscanopy/docker-run-vs-docker-compose-a-practical-migration-guide-4468</link>
      <guid>https://dev.to/opscanopy/docker-run-vs-docker-compose-a-practical-migration-guide-4468</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fdocker-run-vs-compose-hero.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fdocker-run-vs-compose-hero.svg" alt="Diagram comparing docker run vs Docker Compose: a single docker run command on the left and the equivalent docker-compose.yml service on the right, with bidirectional convert arrows" width="1200" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You started a Postgres container three weeks ago with a &lt;code&gt;docker run&lt;/code&gt; one-liner. It works. Then you reboot the box, or a teammate needs the same setup, or you want the command in version control — and you realize the only copy of that command is in your shell history, somewhere between an &lt;code&gt;ls&lt;/code&gt; and a &lt;code&gt;kubectl get pods&lt;/code&gt;. This is the moment the &lt;code&gt;docker run&lt;/code&gt; vs &lt;code&gt;docker compose&lt;/code&gt; question stops being academic. The container is fine; the way you launched it is not reproducible.&lt;/p&gt;

&lt;p&gt;This guide walks through both directions: when &lt;code&gt;docker run&lt;/code&gt; is the right call, when to move to a &lt;code&gt;docker-compose.yml&lt;/code&gt;, and how to convert a Compose service back to a single run line when you need one. Every flag mapping here matches what the &lt;a href="https://opscanopy.com/docker-run-to-compose/" rel="noopener noreferrer"&gt;Docker Run to Compose converter&lt;/a&gt; actually does, so you can check your own commands against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same container, two ways
&lt;/h2&gt;

&lt;p&gt;Here is a real Postgres container expressed as a &lt;code&gt;docker run&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; pgdata:/var/lib/postgresql/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
  postgres:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the exact same container as a Compose service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=secret&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=app&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fdocker-run-vs-compose-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fdocker-run-vs-compose-diagram.svg" alt="One container shown in two formats — a single docker run command on the left and a docker-compose.yml service tree on the right — with bidirectional convert arrows" width="1200" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same image, same ports, same named volume, same restart policy. The difference is not what runs — it is whether the definition lives in your shell history or in a file you can commit, review, and run again with one command. Note the quotes around &lt;code&gt;"5432:5432"&lt;/code&gt;: YAML would otherwise read a bare &lt;code&gt;5432:5432&lt;/code&gt; as a sexagesimal (base-60) number, which is one of the small bugs hand-conversion loves to introduce.&lt;/p&gt;

&lt;h2&gt;
  
  
  What docker run is good at
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;docker run&lt;/code&gt; wins for anything throwaway. You want a one-shot psql client, a quick Redis to poke at, a base image to drop into for debugging — you do not want to author a YAML file for that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# poke at a fresh redis for thirty seconds&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; redis:7-alpine redis-cli

&lt;span class="c"&gt;# debug inside an image without leaving anything behind&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:/work &lt;span class="nt"&gt;-w&lt;/span&gt; /work ubuntu:24.04 bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--rm&lt;/code&gt; flag matters here: the container deletes itself on exit, so you do not accumulate dead containers from experiments. That is a genuinely &lt;code&gt;docker run&lt;/code&gt;-shaped concern — and notably, &lt;code&gt;--rm&lt;/code&gt; has no Compose equivalent at all, because Compose manages container lifecycle for you. If you paste a command with &lt;code&gt;--rm&lt;/code&gt; into a converter, the honest thing to do is drop it with a warning rather than pretend it maps to something. That is exactly what the converter does.&lt;/p&gt;

&lt;p&gt;The same goes for &lt;code&gt;-d&lt;/code&gt; / &lt;code&gt;--detach&lt;/code&gt;. Detached mode is a property of &lt;em&gt;how you launched the process&lt;/em&gt;, not of the service definition, so it does not belong in the YAML either. We will come back to that in the pitfalls section, because it trips people up in both directions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Compose buys you: when to use Docker Compose
&lt;/h2&gt;

&lt;p&gt;Reach for Compose the moment any of these is true — and "when to use Docker Compose" usually comes down to this list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will run this container more than once and want it reproducible.&lt;/li&gt;
&lt;li&gt;You want the definition in version control and reviewed in a PR.&lt;/li&gt;
&lt;li&gt;You have more than one container that needs to come up together.&lt;/li&gt;
&lt;li&gt;You are tired of remembering a 200-character command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Compose file turns a wall of flags into a reviewable document and a single lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;     &lt;span class="c"&gt;# start everything, detached&lt;/span&gt;
docker compose down      &lt;span class="c"&gt;# stop and remove everything&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt;   &lt;span class="c"&gt;# tail every service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multi-service is where the gap really opens. Two &lt;code&gt;docker run&lt;/code&gt; commands that need to talk to each other force you to hand-manage a network, start them in the right order, and remember both lines. Compose makes the relationship declarative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myorg/api:1.4.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://db:5432/app&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;api&lt;/code&gt; service reaches the database at the hostname &lt;code&gt;db&lt;/code&gt; with zero extra wiring. That is the implicit default network doing its job — more on that below. And because the whole thing is a file, you can lint the CI that builds and ships it; if your pipeline runs &lt;code&gt;docker compose up&lt;/code&gt; in a job, the &lt;a href="https://opscanopy.com/gitlab-ci-validator/" rel="noopener noreferrer"&gt;GitLab CI Validator&lt;/a&gt; will catch a malformed &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; before the runner does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fph0y1lgbcc0wm6xhmxas.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fph0y1lgbcc0wm6xhmxas.webp" alt="Synthwave illustration: a docker run one-liner on one retro computer migrating along a neon arrow to a multi-container Docker Compose stack on another" width="799" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Going the other way: Compose to docker run
&lt;/h2&gt;

&lt;p&gt;Migration is not a one-way street. You will hit cases where you have a Compose service but need a single &lt;code&gt;docker run&lt;/code&gt; line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A teammate on a box without your Compose file, who just needs the container up &lt;em&gt;now&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A support ticket or runbook where one copy-paste command beats "clone the repo, then run compose."&lt;/li&gt;
&lt;li&gt;A constrained CI step or remote host where pulling the whole project is overkill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Converting a &lt;code&gt;compose service to docker run&lt;/code&gt; is mechanical but fiddly to do by hand. Take the Redis service with a healthcheck:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cache&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379:6379"&lt;/span&gt;
    &lt;span class="na"&gt;mem_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256m&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app=web&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;redis-cli&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The equivalent command rebuilds every field — and crucially, it is emitted detached by default, because a long-lived service is almost never something you want hogging your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; cache &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 &lt;span class="nt"&gt;-m&lt;/span&gt; 256m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-cmd&lt;/span&gt; &lt;span class="s1"&gt;'redis-cli ping'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-interval&lt;/span&gt; 10s &lt;span class="nt"&gt;--health-timeout&lt;/span&gt; 3s &lt;span class="nt"&gt;--health-retries&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
  redis:7-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The healthcheck block expands back into the discrete &lt;code&gt;--health-*&lt;/code&gt; flags; &lt;code&gt;mem_limit&lt;/code&gt; becomes &lt;code&gt;-m&lt;/code&gt;; labels become &lt;code&gt;-l&lt;/code&gt;. The converter prepends &lt;code&gt;docker run -d&lt;/code&gt; for you precisely because the service was meant to run in the background. The one thing to watch: Compose-only keys like &lt;code&gt;depends_on&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, and &lt;code&gt;deploy&lt;/code&gt; have no command equivalent, so a faithful converter reports them as warnings rather than inventing flags that do not exist. If your service has &lt;code&gt;build:&lt;/code&gt;, you run &lt;code&gt;docker build&lt;/code&gt; first and feed the resulting tag to &lt;code&gt;docker run&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating a real command step by step
&lt;/h2&gt;

&lt;p&gt;Let's take a non-trivial &lt;code&gt;docker run&lt;/code&gt; line and walk the migration end to end. Here is an app container on a user-defined network with adjusted capabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; backend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;NODE_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-add&lt;/span&gt; NET_ADMIN &lt;span class="nt"&gt;--cap-drop&lt;/span&gt; ALL &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--add-host&lt;/span&gt; db:10.0.0.5 &lt;span class="se"&gt;\&lt;/span&gt;
  myorg/api:1.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1 — tokenise, don't eyeball.&lt;/strong&gt; The command is split shell-style: quotes and backslash-newline continuations are honored, and bundled short flags like &lt;code&gt;-it&lt;/code&gt; are expanded into &lt;code&gt;-i -t&lt;/code&gt;. The first non-flag token (&lt;code&gt;myorg/api:1.4.0&lt;/code&gt;) is the image; anything after it would be the container command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — classify each flag onto a key.&lt;/strong&gt; Ports go to &lt;code&gt;ports&lt;/code&gt;, &lt;code&gt;-e&lt;/code&gt; to &lt;code&gt;environment&lt;/code&gt;, &lt;code&gt;--cap-add&lt;/code&gt;/&lt;code&gt;--cap-drop&lt;/code&gt; to &lt;code&gt;cap_add&lt;/code&gt;/&lt;code&gt;cap_drop&lt;/code&gt;, &lt;code&gt;--add-host&lt;/code&gt; to &lt;code&gt;extra_hosts&lt;/code&gt;, and &lt;code&gt;--network backend&lt;/code&gt; to the &lt;code&gt;networks&lt;/code&gt; list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — read the result.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myorg/api:1.4.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;extra_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db:10.0.0.5&lt;/span&gt;
    &lt;span class="na"&gt;cap_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NET_ADMIN&lt;/span&gt;
    &lt;span class="na"&gt;cap_drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing the converter deliberately does &lt;em&gt;not&lt;/em&gt; do: invent a top-level &lt;code&gt;networks:&lt;/code&gt; section you did not ask for. The &lt;code&gt;backend&lt;/code&gt; network shows up under the service, exactly as named. If &lt;code&gt;backend&lt;/code&gt; is a network you created with &lt;code&gt;docker network create&lt;/code&gt;, you will need to declare it as &lt;code&gt;external&lt;/code&gt; at the top level yourself — the tool won't guess at infrastructure you didn't write down. That restraint is the point; a converter that hallucinates structure is worse than one that converts only what you gave it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pitfalls when migrating
&lt;/h2&gt;

&lt;p&gt;The flags themselves map cleanly. The behavior around them is where migrations quietly go wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  The implicit default network
&lt;/h3&gt;

&lt;p&gt;A bare &lt;code&gt;docker run&lt;/code&gt; with no &lt;code&gt;--network&lt;/code&gt; attaches the container to the default &lt;code&gt;bridge&lt;/code&gt; network, where containers reach each other only by IP. Compose is different: it creates a &lt;em&gt;project-scoped&lt;/em&gt; network and puts every service on it, so services resolve each other by service name (&lt;code&gt;db&lt;/code&gt;, &lt;code&gt;api&lt;/code&gt;) out of the box. That is usually what you want — but it means a &lt;code&gt;docker run&lt;/code&gt; that talked to &lt;code&gt;172.17.0.3&lt;/code&gt; needs to start talking to &lt;code&gt;db&lt;/code&gt; once it is a Compose service. Migrating the flag is easy; migrating the assumption that "there's one flat bridge" is the part that bites.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restart policy differences
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;--restart&lt;/code&gt; maps straight across — &lt;code&gt;no&lt;/code&gt;, &lt;code&gt;always&lt;/code&gt;, &lt;code&gt;on-failure&lt;/code&gt;, and &lt;code&gt;unless-stopped&lt;/code&gt; all carry over to &lt;code&gt;restart:&lt;/code&gt; unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subtlety: with &lt;code&gt;docker run&lt;/code&gt;, the restart policy is the &lt;em&gt;only&lt;/em&gt; thing keeping your container alive across a daemon restart. With Compose, the same &lt;code&gt;restart:&lt;/code&gt; value applies, but you also get &lt;code&gt;docker compose up&lt;/code&gt;/&lt;code&gt;down&lt;/code&gt; as an explicit lifecycle. Don't assume &lt;code&gt;restart: always&lt;/code&gt; means "Compose will bring this back after I run &lt;code&gt;down&lt;/code&gt;" — &lt;code&gt;down&lt;/code&gt; removes the container regardless. The restart policy governs crashes and reboots, not your own teardown commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  env_file vs -e
&lt;/h3&gt;

&lt;p&gt;Inline &lt;code&gt;-e KEY=value&lt;/code&gt; flags become an &lt;code&gt;environment:&lt;/code&gt; list, and &lt;code&gt;--env-file path&lt;/code&gt; becomes &lt;code&gt;env_file:&lt;/code&gt;. They are not interchangeable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myorg/api:1.4.0&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env.production&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;    &lt;span class="c1"&gt;# wins over the same key in env_file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inline values are visible in the file and in &lt;code&gt;docker inspect&lt;/code&gt;; an &lt;code&gt;env_file&lt;/code&gt; keeps secret-bearing values out of the YAML and out of your shell history. When you migrate, this is a good moment to move secrets from &lt;code&gt;-e&lt;/code&gt; flags into an &lt;code&gt;env_file&lt;/code&gt;. While you're there, make sure the committed &lt;code&gt;.env.example&lt;/code&gt; actually matches the keys your service reads — the &lt;a href="https://opscanopy.com/env-example-checker/" rel="noopener noreferrer"&gt;Env Example Checker&lt;/a&gt; diffs a real &lt;code&gt;.env&lt;/code&gt; against its example so a missing key doesn't surface as a crash on a fresh checkout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detached mode
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;-d&lt;/code&gt; / &lt;code&gt;--detach&lt;/code&gt; does not exist in a Compose file, because detaching is a launch-time choice, not a property of the service. Going &lt;code&gt;docker run → compose&lt;/code&gt;, the &lt;code&gt;-d&lt;/code&gt; is dropped (you run &lt;code&gt;docker compose up -d&lt;/code&gt; instead). Going &lt;code&gt;compose → docker run&lt;/code&gt;, a faithful converter &lt;em&gt;adds&lt;/em&gt; &lt;code&gt;-d&lt;/code&gt; back, because a service definition almost always describes a long-running process. Both behaviors are correct; they just look asymmetric until you see why. If you find a stray &lt;code&gt;-d&lt;/code&gt; "missing" from generated YAML, that is the tool being right, not losing your flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert both directions instantly
&lt;/h2&gt;

&lt;p&gt;Doing this by hand is fine for one container. It stops being fine when you are translating a wall of &lt;code&gt;-p&lt;/code&gt;, &lt;code&gt;-v&lt;/code&gt;, and &lt;code&gt;-e&lt;/code&gt; flags under time pressure and a mis-nested list or an unquoted port slips through.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://opscanopy.com/docker-run-to-compose/" rel="noopener noreferrer"&gt;Docker Run to Compose converter&lt;/a&gt; does the mechanical part both ways: paste a &lt;code&gt;docker run&lt;/code&gt; command to get the equivalent &lt;code&gt;docker-compose.yml&lt;/code&gt; service, or paste a Compose service to rebuild the run line — including ports, volumes, environment, networks, capabilities, resource limits, and healthchecks. It tells you about the flags and keys that genuinely don't map instead of dropping them silently, and it runs entirely in your browser, so commands that name private registries or carry secret-bearing environment variables never leave the tab.&lt;/p&gt;

&lt;p&gt;Migrate the command, read the warnings, commit the file.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>dockercompose</category>
      <category>containers</category>
    </item>
    <item>
      <title>Why Did Prometheus Drop My Target? Debugging relabel_configs</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Mon, 27 Jul 2026 17:28:51 +0000</pubDate>
      <link>https://dev.to/opscanopy/why-did-prometheus-drop-my-target-debugging-relabelconfigs-56fk</link>
      <guid>https://dev.to/opscanopy/why-did-prometheus-drop-my-target-debugging-relabelconfigs-56fk</guid>
      <description>&lt;p&gt;You added a new exporter, reloaded Prometheus, opened &lt;code&gt;/targets&lt;/code&gt;, and it isn't there. No error in the logs. The scrape config parsed fine. The exporter is up and you can &lt;code&gt;curl&lt;/code&gt; its &lt;code&gt;/metrics&lt;/code&gt; by hand. But Prometheus dropped your target and won't tell you why. Or worse — the target shows up, but a label you depend on for routing or dashboards has silently disappeared. Both symptoms almost always trace back to one place: &lt;code&gt;relabel_configs&lt;/code&gt;. This post walks through how to debug &lt;code&gt;relabel_configs&lt;/code&gt;, where it differs from &lt;code&gt;metric_relabel_configs&lt;/code&gt;, and the handful of mistakes that account for nearly every dropped target.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom: a missing target in /targets, or a label that vanished
&lt;/h2&gt;

&lt;p&gt;There are two distinct failures and it helps to name them before you start digging.&lt;/p&gt;

&lt;p&gt;The first is the &lt;strong&gt;dropped target&lt;/strong&gt;: it never appears under &lt;code&gt;/targets&lt;/code&gt; at all, not even in a "down" state. Service discovery found it, but a &lt;code&gt;keep&lt;/code&gt; or &lt;code&gt;drop&lt;/code&gt; rule removed it before the scrape ran. Prometheus does not log this — from its point of view, nothing went wrong.&lt;/p&gt;

&lt;p&gt;The second is the &lt;strong&gt;disappearing label&lt;/strong&gt;: the target scrapes fine, but a label you expected is gone, or got overwritten with something unexpected. You see this in &lt;code&gt;/targets&lt;/code&gt; (hover the labels) or by querying the series and noticing the dimension you wanted to group by isn't there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The target you expect is simply absent from the list:&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:9090/api/v1/targets | jq &lt;span class="s1"&gt;'.data.activeTargets[].labels.job'&lt;/span&gt;
&lt;span class="c"&gt;# "node-exporter"&lt;/span&gt;
&lt;span class="c"&gt;# "blackbox"&lt;/span&gt;
&lt;span class="c"&gt;#   ← your "api" job never shows up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a target is silently absent, the cause is upstream of the scrape. That's relabeling. The good news: relabeling is deterministic. Given the same input labels and the same rules, you get the same outcome every time, which means you can reproduce it offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  relabel_configs vs metric_relabel_configs: where each one runs
&lt;/h2&gt;

&lt;p&gt;The two config blocks apply the &lt;em&gt;exact same&lt;/em&gt; relabeling actions and semantics. The only difference is &lt;strong&gt;where&lt;/strong&gt; in the scrape lifecycle they run — and that difference decides which symptom you're debugging.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;relabel_configs&lt;/code&gt; runs &lt;strong&gt;at scrape time, before the scrape&lt;/strong&gt;, on the target labels coming from service discovery. These are the labels that decide &lt;em&gt;whether a target gets scraped at all&lt;/em&gt; and what its identity (&lt;code&gt;job&lt;/code&gt;, &lt;code&gt;instance&lt;/code&gt;, &lt;code&gt;__address__&lt;/code&gt;) is. A &lt;code&gt;keep&lt;/code&gt;/&lt;code&gt;drop&lt;/code&gt; here removes a whole target. This is the block to inspect when a target is missing from &lt;code&gt;/targets&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;metric_relabel_configs&lt;/code&gt; runs &lt;strong&gt;after the scrape&lt;/strong&gt;, on every sample's label set as it's ingested. A &lt;code&gt;keep&lt;/code&gt;/&lt;code&gt;drop&lt;/code&gt; here removes individual time series, not the target. This is the block to inspect when the target is present but specific series or labels are missing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fdebug-prometheus-relabeling-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fdebug-prometheus-relabeling-diagram.svg" alt="The Prometheus scrape lifecycle showing service discovery and __meta_ labels, then relabel_configs which can drop a whole target, then the scrape, then metric_relabel_configs which can drop individual samples, then the TSDB." width="1200" height="620"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes_sd_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod&lt;/span&gt;

    &lt;span class="c1"&gt;# Runs BEFORE the scrape, on discovery labels (__meta_*, __address__).&lt;/span&gt;
    &lt;span class="c1"&gt;# A keep/drop here removes the whole target.&lt;/span&gt;
    &lt;span class="na"&gt;relabel_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__meta_kubernetes_pod_annotation_prometheus_io_scrape&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
        &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;

    &lt;span class="c1"&gt;# Runs AFTER the scrape, on each sample. A drop here removes series,&lt;/span&gt;
    &lt;span class="c1"&gt;# not the target.&lt;/span&gt;
    &lt;span class="na"&gt;metric_relabel_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__name__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;drop&lt;/span&gt;
        &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go_gc_.*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your target is missing, you never reach &lt;code&gt;metric_relabel_configs&lt;/code&gt; — debug &lt;code&gt;relabel_configs&lt;/code&gt; first. If the target is present but a series is gone, it's the other block. Getting this distinction right is half the battle when you're searching "metric_relabel_configs vs relabel_configs" at 2 a.m.&lt;/p&gt;

&lt;h2&gt;
  
  
  The usual suspects
&lt;/h2&gt;

&lt;p&gt;Almost every dropped target comes from one of these. Each is easy to make and invisible until you reproduce it.&lt;/p&gt;

&lt;h3&gt;
  
  
  A keep regex that does not match (because the regex is anchored)
&lt;/h3&gt;

&lt;p&gt;This is the number-one cause. &lt;strong&gt;Prometheus anchors every relabel regex&lt;/strong&gt; — internally it wraps your pattern as &lt;code&gt;^(?:&amp;lt;your regex&amp;gt;)$&lt;/code&gt;. The pattern must match the &lt;em&gt;entire&lt;/em&gt; joined source value, not a substring.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;          &lt;span class="c1"&gt;# anchored to ^(?:api)$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps a target whose &lt;code&gt;job&lt;/code&gt; is exactly &lt;code&gt;api&lt;/code&gt;. It does &lt;strong&gt;not&lt;/strong&gt; keep &lt;code&gt;api-server&lt;/code&gt;, &lt;code&gt;api-prod&lt;/code&gt;, or &lt;code&gt;payments-api&lt;/code&gt;. With a &lt;code&gt;keep&lt;/code&gt; action, anything that fails to match is dropped — so your &lt;code&gt;api-server&lt;/code&gt; target silently vanishes. The fix is to match what you actually mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.*&lt;/span&gt;        &lt;span class="c1"&gt;# ^(?:api.*)$ — matches api, api-server, api-prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A drop that is too broad
&lt;/h3&gt;

&lt;p&gt;The mirror image. An unanchored mental model plus a greedy regex catches more than intended:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__name__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;drop&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.*_bucket&lt;/span&gt;   &lt;span class="c1"&gt;# drops EVERY *_bucket series, including ones you need&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;keep&lt;/code&gt; is an allow-list gate; &lt;code&gt;drop&lt;/code&gt; is a deny-list gate. A too-broad &lt;code&gt;drop&lt;/code&gt; in &lt;code&gt;metric_relabel_configs&lt;/code&gt; quietly deletes series you wanted to keep, and you only notice when a dashboard goes blank.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong source_labels, or the wrong join
&lt;/h3&gt;

&lt;p&gt;When a rule lists multiple &lt;code&gt;source_labels&lt;/code&gt;, Prometheus joins their values with the &lt;strong&gt;separator&lt;/strong&gt; — which defaults to a single semicolon &lt;code&gt;;&lt;/code&gt; — &lt;em&gt;before&lt;/em&gt; matching the regex. If you forget the separator, your regex never matches the joined string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# job="api", instance="10.0.0.1:9090" joins to "api;10.0.0.1:9090"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;          &lt;span class="c1"&gt;# ✗ never matches "api;10.0.0.1:9090"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need a regex that accounts for the &lt;code&gt;;&lt;/code&gt;, e.g. &lt;code&gt;api;.*&lt;/code&gt;. A missing source label isn't an error either — Prometheus treats an absent label as the empty string when joining, so &lt;code&gt;source_labels: [does_not_exist]&lt;/code&gt; joins to &lt;code&gt;""&lt;/code&gt; and a &lt;code&gt;keep: regex: ".+"&lt;/code&gt; drops everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  A replacement that overwrote &lt;strong&gt;address&lt;/strong&gt; (or deleted a label)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;replace&lt;/code&gt; has a subtle, real behavior: &lt;strong&gt;if the regex doesn't match, the label is left unchanged; but if it matches and the expanded replacement is the empty string, the target label is deleted, not set to blank.&lt;/strong&gt; Overwrite &lt;code&gt;__address__&lt;/code&gt; with an empty value and the target effectively loses its scrape address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# If prometheus_io_port is absent, the joined value won't match this regex,&lt;/span&gt;
&lt;span class="c1"&gt;# so __address__ is left alone. But a regex that DOES match and expands to ""&lt;/span&gt;
&lt;span class="c1"&gt;# would DELETE __address__ entirely.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__address__&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;__meta_kubernetes_pod_annotation_prometheus_io_port&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replace&lt;/span&gt;
  &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;([^:]+)(?::\d+)?;(\d+)&lt;/span&gt;
  &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$1:$2&lt;/span&gt;
  &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__address__&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most insidious one, because an empty &lt;code&gt;instance&lt;/code&gt; or &lt;code&gt;__address__&lt;/code&gt; doesn't throw — it just produces a target that can't be scraped or that collides with another.&lt;/p&gt;

&lt;h2&gt;
  
  
  A debugging workflow
&lt;/h2&gt;

&lt;p&gt;When a target is missing, work top-down. The whole point is to recover the &lt;em&gt;exact input&lt;/em&gt; the rules saw, then replay the rules against it.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Dump the target labels, including _&lt;em&gt;meta&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Prometheus exposes the pre-relabel discovery labels — the &lt;code&gt;__meta_*&lt;/code&gt; labels — but only for targets that survived relabeling, so a fully dropped target won't appear. The trick is to reload with the relabel rules temporarily removed (or pared down to a single permissive &lt;code&gt;keep&lt;/code&gt;), then read the raw discovery labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Show discovered labels for the job, including the __meta_* set the&lt;/span&gt;
&lt;span class="c"&gt;# relabel rules actually see as input.&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'localhost:9090/api/v1/targets?state=active'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.data.activeTargets[]
        | select(.discoveredLabels.job=="api")
        | .discoveredLabels'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;discoveredLabels&lt;/code&gt; is the input to your &lt;code&gt;relabel_configs&lt;/code&gt;. &lt;code&gt;labels&lt;/code&gt; is the output. If a target is dropped entirely, you can also read the service discovery state directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:9090/api/v1/targets/metadata &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null  &lt;span class="c"&gt;# sanity check API is up&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'localhost:9090/service-discovery'&lt;/span&gt; &lt;span class="c"&gt;# the SD page shows pre-relabel labels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Test the rules against those labels
&lt;/h3&gt;

&lt;p&gt;Now you have the input. Paste the &lt;code&gt;__meta_*&lt;/code&gt; labels and your &lt;code&gt;relabel_configs&lt;/code&gt; into &lt;a href="https://opscanopy.com/prometheus-relabel-tester/" rel="noopener noreferrer"&gt;the Prometheus Relabel Tester&lt;/a&gt; and run them. It applies the rules exactly the way Prometheus does — anchored regex, &lt;code&gt;;&lt;/code&gt; separator, &lt;code&gt;$1&lt;/code&gt;/&lt;code&gt;${1}&lt;/code&gt; expansion — and tells you, per label set, the resulting labels, which ones were added, changed, or removed, and whether the target was dropped (and by which rule).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Bisect the rule list
&lt;/h3&gt;

&lt;p&gt;If you have a long chain, comment out the second half of the rules and re-run. If the target survives, the culprit is in the half you removed; if it still drops, it's in the half that's left. Halve again. Because relabeling is a deterministic top-to-bottom chain — each rule sees the output of the one before it — bisection converges fast, usually in two or three rounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: the disappearing target, found and fixed
&lt;/h2&gt;

&lt;p&gt;Here's a real shape of this bug. You discover a pod, want to keep only opted-in pods, and route by environment. The target never shows up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;relabel_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__meta_kubernetes_pod_annotation_prometheus_io_scrape&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
    &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__meta_kubernetes_pod_label_env&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
    &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The discovery labels for the target you expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;__meta_kubernetes_pod_annotation_prometheus_io_scrape="true"
__meta_kubernetes_pod_label_app="api"
__meta_kubernetes_pod_label_env="production"
__address__="10.0.0.5:8080"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that input through the rules. The first &lt;code&gt;keep&lt;/code&gt; passes — &lt;code&gt;prometheus_io_scrape&lt;/code&gt; is exactly &lt;code&gt;"true"&lt;/code&gt;. The second &lt;code&gt;keep&lt;/code&gt; joins to &lt;code&gt;production&lt;/code&gt; and tries to match &lt;code&gt;^(?:prod)$&lt;/code&gt;. It doesn't. &lt;code&gt;production&lt;/code&gt; is not &lt;code&gt;prod&lt;/code&gt;, the regex is anchored, and &lt;code&gt;keep&lt;/code&gt; drops anything that fails to match. &lt;strong&gt;Rule 2 dropped the target.&lt;/strong&gt; The tester flags exactly that: dropped by rule 2, action &lt;code&gt;keep&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix is to match the real value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__meta_kubernetes_pod_label_env&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep&lt;/span&gt;
    &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prod.*&lt;/span&gt;       &lt;span class="c1"&gt;# ^(?:prod.*)$ — now matches "production"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run. The target survives, carries &lt;code&gt;__address__="10.0.0.5:8080"&lt;/code&gt;, and appears in &lt;code&gt;/targets&lt;/code&gt;. Total time: under a minute, with no Prometheus reload and no waiting on a scrape interval.&lt;/p&gt;

&lt;p&gt;While you're cleaning up, the same chain often promotes pod labels and prunes discovery metadata. Note that &lt;code&gt;labelmap&lt;/code&gt; operates on label &lt;em&gt;names&lt;/em&gt;, copying matching labels to a new name, and &lt;code&gt;labeldrop&lt;/code&gt; removes labels whose names match — useful, but another place a label you wanted can quietly disappear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Promote pod labels: __meta_kubernetes_pod_label_app="api" → app="api"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;labelmap&lt;/span&gt;
    &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__meta_kubernetes_pod_label_(.+)&lt;/span&gt;

  &lt;span class="c1"&gt;# Strip leftover discovery metadata before storage.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;labeldrop&lt;/span&gt;
    &lt;span class="na"&gt;regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__meta_.+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Catch it before deploy
&lt;/h2&gt;

&lt;p&gt;The fastest debugging loop is the one that never reaches a live Prometheus. The reason relabeling is so easy to get wrong is that it fails silently: there's no parse error, no log line, just a target that isn't there. The only honest check is to run the rules against representative input and read the output — the same idea behind testing any behavioral config rather than trusting a schema lint.&lt;/p&gt;

&lt;p&gt;When you're staring at a "prometheus dropped target" mystery or a "prometheus label disappeared" report, grab the &lt;code&gt;discoveredLabels&lt;/code&gt; from the API, paste them with your rules into &lt;a href="https://opscanopy.com/prometheus-relabel-tester/" rel="noopener noreferrer"&gt;the Prometheus Relabel Tester&lt;/a&gt;, and watch which rule does the damage — it runs entirely in your browser, so internal scrape configs and target metadata never leave your tab.&lt;/p&gt;

&lt;p&gt;Once the labels are right, the rest of the observability chain follows. Break down a query that depends on those labels with &lt;a href="https://opscanopy.com/promql-explainer/" rel="noopener noreferrer"&gt;the PromQL Explainer&lt;/a&gt;, or confirm an alert on the resulting series lands in the right place with &lt;a href="https://opscanopy.com/alertmanager-route-tester/" rel="noopener noreferrer"&gt;the Alertmanager Route Tester&lt;/a&gt;. Shape the labels first; everything downstream depends on getting that step correct.&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>observability</category>
      <category>relabeling</category>
    </item>
    <item>
      <title>Why Isnt My Alert Reaching the Right Receiver? Debugging Alertmanager Routing</title>
      <dc:creator>opscanopy</dc:creator>
      <pubDate>Mon, 27 Jul 2026 17:28:17 +0000</pubDate>
      <link>https://dev.to/opscanopy/why-isnt-my-alert-reaching-the-right-receiver-debugging-alertmanager-routing-15o4</link>
      <guid>https://dev.to/opscanopy/why-isnt-my-alert-reaching-the-right-receiver-debugging-alertmanager-routing-15o4</guid>
      <description>&lt;p&gt;You shipped a new alerting rule, it fired in production, and the page went to the wrong team — or nobody got paged at all. The rule is correct and the alert is firing, yet your Alertmanager wrong-receiver problem is real: the notification landed somewhere you did not expect. When Alertmanager is not routing the way you intended, the bug is almost never in the alert. It is in the &lt;code&gt;route&lt;/code&gt; tree, and routing trees are code you cannot easily step through.&lt;/p&gt;

&lt;p&gt;Alertmanager dispatches every alert by walking a tree of routes. The root is the catch-all every alert enters; from there it descends into child routes whose matchers hold against the alert's labels. Get the walk wrong and the alert silently lands on the wrong leaf. This post covers the five bugs that cause it, and how to walk the tree yourself — no &lt;code&gt;amtool&lt;/code&gt;, no reload, no live instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom: silent pages, or the wrong team gets paged
&lt;/h2&gt;

&lt;p&gt;Two shapes of the same problem. Either an alert you expected to page the database team went to a catch-all Slack channel nobody watches, or a &lt;code&gt;severity=critical&lt;/code&gt; alert produced no page at all. Both come from the same root cause: the route the alert &lt;em&gt;actually&lt;/em&gt; matched is not the route you &lt;em&gt;think&lt;/em&gt; it matched.&lt;/p&gt;

&lt;p&gt;Here is the tree most people start from — the canonical routing example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cluster'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;group_wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
  &lt;span class="na"&gt;group_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
  &lt;span class="na"&gt;repeat_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4h&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-X-mails'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-DB-pages'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-Y-mails'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An alert with &lt;code&gt;service=database&lt;/code&gt; reaches &lt;code&gt;team-DB-pages&lt;/code&gt;. Simple enough — until the tree grows, siblings get reordered, someone adds a regex, and the walk stops doing what you read off the page. The fix is always the same: stop reasoning in your head and walk the tree against the exact labels the alert carries. Every bug below is a different way the walk surprises you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: first match wins and you forgot continue: true
&lt;/h2&gt;

&lt;p&gt;This is the most common Alertmanager-not-routing bug. Within a matched route, child routes are evaluated &lt;strong&gt;top to bottom&lt;/strong&gt;, and the alert descends into the &lt;strong&gt;first&lt;/strong&gt; matching child — then the sibling scan stops. Later siblings never run.&lt;/p&gt;

&lt;p&gt;That bites hardest when you want an alert to reach two receivers — say, every critical alert mirrored to an audit receiver &lt;em&gt;and&lt;/em&gt; routed to the owning team:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;all-critical-audit'&lt;/span&gt;
      &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity="critical"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-Y-pages'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fire an alert with &lt;code&gt;team=backend&lt;/code&gt; and &lt;code&gt;severity=critical&lt;/code&gt;. It matches the first sibling, &lt;code&gt;all-critical-audit&lt;/code&gt;, and the scan stops there. &lt;code&gt;team-Y-pages&lt;/code&gt; is never reached, so the backend team is never paged. The audit channel logged it, so it &lt;em&gt;looks&lt;/em&gt; like routing worked — which is exactly why this one is hard to spot.&lt;/p&gt;

&lt;p&gt;The fix is one line. A matched route with &lt;code&gt;continue: true&lt;/code&gt; does not stop the sibling scan, so the alert keeps falling through to later matching siblings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;all-critical-audit'&lt;/span&gt;
        &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity="critical"&lt;/span&gt;
        &lt;span class="na"&gt;continue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;        &lt;span class="c1"&gt;# keep going to later siblings&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-Y-pages'&lt;/span&gt;
        &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now both fire. An alert can only reach more than one receiver when &lt;code&gt;continue: true&lt;/code&gt; is set on a matched route; without it, the first matching sibling always wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 2: the matcher does not match (regex, quoting, a missing label)
&lt;/h2&gt;

&lt;p&gt;If the alert silently skips a route you were sure it would hit, the matcher probably is not matching. Three traps account for almost all of these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regexes are fully anchored.&lt;/strong&gt; Both &lt;code&gt;match_re&lt;/code&gt; and the &lt;code&gt;=~&lt;/code&gt; / &lt;code&gt;!~&lt;/code&gt; operators wrap your pattern as &lt;code&gt;^(?:…)$&lt;/code&gt;. A partial pattern never matches a longer value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;env=~"staging"&lt;/span&gt;      &lt;span class="c1"&gt;# env=staging-eu does NOT match — anchored to exactly "staging"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;env=~"staging-.*"&lt;/span&gt;   &lt;span class="c1"&gt;# env=staging-eu matches now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A missing label is the empty string.&lt;/strong&gt; Alertmanager treats a label absent on the alert as &lt;code&gt;""&lt;/code&gt;, so &lt;code&gt;team=""&lt;/code&gt; matches an alert with &lt;em&gt;no&lt;/em&gt; &lt;code&gt;team&lt;/code&gt; label and &lt;code&gt;team!=""&lt;/code&gt; requires it to be present and non-empty. If you write &lt;code&gt;match: { team: frontend }&lt;/code&gt; but the alert never sets a &lt;code&gt;team&lt;/code&gt; label, the matcher compares &lt;code&gt;frontend&lt;/code&gt; against &lt;code&gt;""&lt;/code&gt;, fails, and the route is skipped — you fall through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operators and quoting in &lt;code&gt;matchers:&lt;/code&gt; strings.&lt;/strong&gt; The modern &lt;code&gt;matchers:&lt;/code&gt; form takes strings like &lt;code&gt;foo="bar"&lt;/code&gt;, &lt;code&gt;foo=~"re"&lt;/code&gt;, &lt;code&gt;foo!="x"&lt;/code&gt;, and &lt;code&gt;foo!~"re"&lt;/code&gt;; the value can be quoted or bare. The two-character operators (&lt;code&gt;=~&lt;/code&gt;, &lt;code&gt;!~&lt;/code&gt;, &lt;code&gt;!=&lt;/code&gt;) are matched before the single &lt;code&gt;=&lt;/code&gt;, so &lt;code&gt;severity!="info"&lt;/code&gt; parses as a not-equal. Get the quoting wrong — leave a quote open, say — and the matcher is invalid; an invalid matcher cannot hold, so the route is skipped.&lt;/p&gt;

&lt;p&gt;Here is a matcher route that combines a regex with an inequality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;env'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;staging-slack'&lt;/span&gt;
      &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;env=~"staging-.*"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity!="info"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prod-pager'&lt;/span&gt;
      &lt;span class="na"&gt;match_re&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prod-.*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All matchers on a route must hold for it to match — it is a logical AND. An alert with &lt;code&gt;env=staging-eu&lt;/code&gt; and &lt;code&gt;severity=warning&lt;/code&gt; reaches &lt;code&gt;staging-slack&lt;/code&gt;: the anchored &lt;code&gt;staging-.*&lt;/code&gt; matches and &lt;code&gt;severity&lt;/code&gt; is not &lt;code&gt;info&lt;/code&gt;. Change &lt;code&gt;severity&lt;/code&gt; to &lt;code&gt;info&lt;/code&gt; and the second matcher fails, so the whole route is skipped.&lt;/p&gt;

&lt;p&gt;If your alert rules carry the wrong labels in the first place — or are missing the ones your routes match on — fix that upstream. The &lt;a href="https://opscanopy.com/prometheus-relabel-tester/" rel="noopener noreferrer"&gt;Prometheus Relabel Tester&lt;/a&gt; previews exactly what labels survive your relabel rules before they ever reach the route tree.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9d678grdp4bzwu0if3f.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9d678grdp4bzwu0if3f.webp" alt="Synthwave illustration of debugging Alertmanager routing: an alert rides a neon route tree through a first-match-wins gate, past the classic traps — missing continue, matcher regex, catch-all defaults" width="799" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 3: a catch-all default route swallows everything before your route is reached
&lt;/h2&gt;

&lt;p&gt;An Alertmanager catch-all route is supposed to be a safety net — the receiver that fires when nothing more specific matches. But a catch-all placed &lt;em&gt;above&lt;/em&gt; a specific sibling, instead of below it, turns into a trap. Combined with first-match-wins, a broad rule at the top shadows every specific rule beneath it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Trap: the broad rule above shadows the specific one&lt;/span&gt;
&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;catch-all&lt;/span&gt;
    &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity=~".*"'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# matches everything&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-pager&lt;/span&gt;             &lt;span class="c1"&gt;# NEVER reached&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;database&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;severity=~".*"&lt;/code&gt; matches any alert that has a &lt;code&gt;severity&lt;/code&gt; label (anchored, but &lt;code&gt;.*&lt;/code&gt; covers the whole value). It is the first sibling, so the scan stops there — &lt;code&gt;db-pager&lt;/code&gt; is dead code. The database team never pages.&lt;/p&gt;

&lt;p&gt;There are two correct ways to think about a catch-all. Either put your specific routes first and the broad one last:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fix: specific first, broad last&lt;/span&gt;
&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-pager&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;database&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;catch-all&lt;/span&gt;
    &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity=~".*"'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or rely on the real catch-all you already have — the root route's own &lt;code&gt;receiver&lt;/code&gt;. When no child route matches, the route the alert is sitting in becomes the terminal match and &lt;em&gt;its&lt;/em&gt; receiver fires. The root always sets a default &lt;code&gt;receiver&lt;/code&gt;, so an alert that matches no child still lands somewhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;     &lt;span class="c1"&gt;# the true catch-all&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-X-mails'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;frontend&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-Y-mails'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;backend&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An alert with &lt;code&gt;team=platform&lt;/code&gt; matches neither child. It does not error and it does not vanish — it falls through to &lt;code&gt;default-receiver&lt;/code&gt;, the catch-all working as intended. The "why didn't my alert route?" cases are usually this: it &lt;em&gt;did&lt;/em&gt; route, straight to the default, because no child matched. If a route resolves to no receiver at all, that is a genuine misconfiguration — Alertmanager requires the root to set a default &lt;code&gt;receiver&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 4: route ordering among siblings
&lt;/h2&gt;

&lt;p&gt;Bug 3 is a catch-all swallowing everything. Bug 4 is the subtler, more general version: among siblings, order &lt;em&gt;always&lt;/em&gt; decides which single route wins, even when both are specific. Because only the first matching sibling is taken (absent &lt;code&gt;continue&lt;/code&gt;), two overlapping matchers in the wrong order route the alert to the wrong team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fdebug-alertmanager-routing-diagram.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopscanopy.com%2Fblog%2Fdebug-alertmanager-routing-diagram.svg" alt="A misrouted alert: on the left the alert hits the route tree and lands on the wrong receiver in red because continue is missing, on the right the corrected tree routes it to the right receiver in green" width="1200" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Consider an alert that is both a database alert and a backend-team alert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# labels: service=database, team=backend, severity=critical&lt;/span&gt;
&lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-Y-pages'&lt;/span&gt;      &lt;span class="c1"&gt;# matches team=backend&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;backend&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team-DB-pages'&lt;/span&gt;     &lt;span class="c1"&gt;# matches service=database&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;database&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both routes' matchers hold against this alert. Order breaks the tie: &lt;code&gt;team-Y-pages&lt;/code&gt; is first, so it wins, and the database on-call (&lt;code&gt;team-DB-pages&lt;/code&gt;) is never reached. Swap the two and the database route wins instead. Neither matcher is wrong — the &lt;em&gt;order&lt;/em&gt; is the bug.&lt;/p&gt;

&lt;p&gt;When two siblings can legitimately both match, you have three choices: put the one you want to win first, make the matchers mutually exclusive (add &lt;code&gt;service!=database&lt;/code&gt; to the backend route, say), or set &lt;code&gt;continue: true&lt;/code&gt; on the first so the alert reaches both. Nesting helps too — a parent matches the broad case and narrows it with children:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default-receiver'&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;web-team'&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
      &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;instance'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;web-team-pager'&lt;/span&gt;
          &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity="critical"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;web-team-slack'&lt;/span&gt;
          &lt;span class="na"&gt;matchers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;severity=~"warning|info"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An alert with &lt;code&gt;service=web&lt;/code&gt; first descends into &lt;code&gt;web-team&lt;/code&gt;, then the nested children pick the receiver by &lt;code&gt;severity&lt;/code&gt;. A &lt;code&gt;severity=critical&lt;/code&gt; web alert walks &lt;code&gt;root → web-team → web-team-pager&lt;/code&gt;. The descent is explicit, so order surprises stay local to one small sibling list instead of hiding across the whole tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 5: grouping makes an alert look missing when it is just batched
&lt;/h2&gt;

&lt;p&gt;Sometimes the alert routed perfectly and you still think it is missing — because grouping batched it with others and the notification has not been sent &lt;em&gt;yet&lt;/em&gt;. Grouping is controlled by &lt;code&gt;group_by&lt;/code&gt;, &lt;code&gt;group_wait&lt;/code&gt;, &lt;code&gt;group_interval&lt;/code&gt;, and &lt;code&gt;repeat_interval&lt;/code&gt;, and all four are &lt;strong&gt;inherited&lt;/strong&gt; down the tree. A child that does not set its own carries the parent's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cluster'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;group_wait&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-pager&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;database&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="c1"&gt;# no group_by here → INHERITS ['alertname', 'cluster']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;db-pager&lt;/code&gt; leaf has no &lt;code&gt;group_by&lt;/code&gt; of its own, so it inherits &lt;code&gt;['alertname', 'cluster']&lt;/code&gt; and a 30s &lt;code&gt;group_wait&lt;/code&gt; from the root. Two consequences trip people up. First, a new group is held for &lt;code&gt;group_wait&lt;/code&gt; before its first notification — so a freshly fired alert that "isn't paging" may just be inside its wait window. Second, if &lt;code&gt;group_by&lt;/code&gt; is too coarse, your alert gets folded into an existing group's notification and looks like it never fired separately.&lt;/p&gt;

&lt;p&gt;Override only where a subtree actually needs different grouping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cluster'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;receiver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-pager&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;database&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;group_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alertname'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cluster'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;database'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The leaf you are reading is not necessarily the grouping that applies. Always resolve the &lt;em&gt;effective&lt;/em&gt; &lt;code&gt;group_by&lt;/code&gt; — the value inherited from the nearest ancestor that set it — before you conclude an alert is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Alertmanager routing without amtool: walk the tree against the alerts labels
&lt;/h2&gt;

&lt;p&gt;You do not need &lt;code&gt;amtool config routes test&lt;/code&gt;, and you do not need to reload a live Alertmanager to do route debugging. The routing walk is deterministic, so you can do it by hand. Take the exact labels off the firing alert and walk the tree top-down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# The labels the alert actually carries (from the Alertmanager UI or API):&lt;/span&gt;
&lt;span class="nv"&gt;alertname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HighLatency
&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;database
&lt;span class="nv"&gt;team&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;backend
&lt;span class="nv"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;critical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, starting at the root:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enter the root.&lt;/strong&gt; Every alert does — it is the catch-all. Note its &lt;code&gt;receiver&lt;/code&gt; and &lt;code&gt;group_by&lt;/code&gt; as the inheritance baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scan children top to bottom.&lt;/strong&gt; For each child, check whether &lt;em&gt;all&lt;/em&gt; its matchers hold against the labels. Remember: regexes are anchored, and a missing label is &lt;code&gt;""&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Descend into the first match.&lt;/strong&gt; That child's subtree is now where you are. If it set &lt;code&gt;continue: true&lt;/code&gt;, keep scanning its later siblings too — those become additional matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If no child matches, you are done.&lt;/strong&gt; The current route is the terminal match; its inherited &lt;code&gt;receiver&lt;/code&gt; fires.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolve inheritance at the leaf.&lt;/strong&gt; The effective &lt;code&gt;receiver&lt;/code&gt; and &lt;code&gt;group_by&lt;/code&gt; come from the nearest ancestor that set them, not necessarily the leaf.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do that for the labels above against the docs tree and you land on &lt;code&gt;team-DB-pages&lt;/code&gt; via &lt;code&gt;service=database&lt;/code&gt;, inheriting &lt;code&gt;group_by&lt;/code&gt; from the root. Doing this walk on paper for a 40-node tree is exactly the error-prone reasoning that produced the bug in the first place — which is the whole reason a tester exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the matching receiver now: an Alertmanager route debugger in the browser
&lt;/h2&gt;

&lt;p&gt;When the tree is more than a few nodes, walk it with the &lt;a href="https://opscanopy.com/alertmanager-route-tester/" rel="noopener noreferrer"&gt;Alertmanager Route Tester&lt;/a&gt; instead of in your head. Paste your route tree — a bare &lt;code&gt;route:&lt;/code&gt; block or a full &lt;code&gt;alertmanager.yml&lt;/code&gt;, of which only the &lt;code&gt;route&lt;/code&gt; block is read — and the sample alert's labels, one &lt;code&gt;key=value&lt;/code&gt; per line. It reproduces the semantics exactly: first-match-wins, &lt;code&gt;continue: true&lt;/code&gt; fan-out, anchored regexes, missing-label-as-empty-string, and grouping inheritance.&lt;/p&gt;

&lt;p&gt;What you get back is every receiver the alert reaches, in evaluation order, each with its route-path breadcrumb from the root down to the matched node, a tag on any match reached only via &lt;code&gt;continue&lt;/code&gt;, and the effective &lt;code&gt;group_by&lt;/code&gt; after inheritance. It is a dry run of dispatch — no notification is sent, nothing is uploaded, and it all runs in your browser, so you can safely paste internal receiver names and private team labels.&lt;/p&gt;

&lt;p&gt;Once the labels are confirmed correct at the source with the &lt;a href="https://opscanopy.com/prometheus-relabel-tester/" rel="noopener noreferrer"&gt;Prometheus Relabel Tester&lt;/a&gt; and your rules are proven to fire with &lt;a href="https://opscanopy.com/loki-alert-rule-tester/" rel="noopener noreferrer"&gt;AlertLint&lt;/a&gt;, the route tree is the last hop to get right. Walk it before it pages anyone — and the next time an alert reaches the wrong receiver, you will know which node sent it there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opscanopy.com/alertmanager-route-tester/" rel="noopener noreferrer"&gt;Open the Alertmanager Route Tester →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>alertmanager</category>
      <category>observability</category>
      <category>alerting</category>
    </item>
  </channel>
</rss>
