Monday, 9 November 2026

vm vs container, k8 vs docker, baremetal





• Virtual Machines (VMs): Virtualize hardware. When you boot an Ubuntu VM, it bundles a full kernel AND the userland. The VM's kernel talks to a hypervisor, which mimics actual physical hardware.

• Containers: Virtualize the OS(k8). 


A container does not have a kernel. An Ubuntu container is only the Ubuntu userland—a collection of file directories, configuration files, system libraries (glibc), and tools like apt, bash, and ls.

When an app inside an Ubuntu container needs to write a file or send a network packet, it passes that instruction to the Ubuntu system library (glibc), which then makes a system call directly to your host machine's Linux kernel



************************************************************************************


VM


Physical server: Has physical resources such as 16 CPU cores, 64 GB RAM, and 1 TB storage.




Hypervisor (Proxmox, VMware, KVM): Allocates virtual resources to VMs. For example, 4 vCPUs, 8 GB RAM, and 100 GB virtual disk.




VM (Ubuntu): Boots its own Linux kernel, which manages the VM's virtual CPU, memory, storage, processes, and networking.




Applications: Applications inside the Ubuntu VM make system calls to the Ubuntu VM's own kernel, not directly to the physical host's kernel.




Execution: The VM's instructions generally execute directly on physical CPU cores through hardware virtualization, while the hypervisor controls CPU scheduling and access to physical resources.


************************************************************************************

Container 


A container does not have a kernel. An Ubuntu container is only the Ubuntu userland—a collection of file directories, configuration files, system libraries (glibc), and tools like apt, bash, and ls.

When an app inside an Ubuntu container needs to write a file or send a network packet, it passes that instruction to the Ubuntu system library (glibc), which then makes a system call directly to your host machine's Linux kernel




*********************************************



KVM: Provides VM virtualization.




Docker: Builds, runs, and manages containers on a machine.




Kubernetes: Orchestrates containers across machines, including deployment, scaling, networking, and recovery. Kubernetes typically communicates with a container runtime such as containerd rather than requiring Docker.


**************************************************


Kernel vs. Operating System

The kernel is the core component of an operating system, responsible for interacting with hardware and managing system resources.


An operating system includes the kernel plus other software, such as system libraries, utilities, and services.


For example, Ubuntu Linux consists of:


Ubuntu Operating System

Applications


nginx, MySQL, Chrome


Userland


bash, apt, glibc, system utilities


Linux Kernel


CPU scheduling, memory, networking, drivers



Physical Hardware



Ubuntu OS = Linux kernel + Ubuntu userland + system services and utilities.



***************************************************************

For standard Linux containers, yes. They require a Linux kernel to execute their system calls.


However, you can use Docker on Windows and macOS as well.





Windows Subsystem for Linux (WSL) can be either, depending on the version. WSL2 is essentially a lightweight virtual machine (VM), not a container.


 A virtual machine (VM) requires a hypervisor or an equivalent virtualization layer to run. However, not every VM needs a separate, standalone hypervisor application.


The hypervisor is responsible for providing and managing virtual hardware, including CPU, memory, storage, and network devices.


*********************



Type 1 — Bare Metal

Virtual Machines


Ubuntu / Windows


Hypervisor


VMware ESXi / Hyper-V


Physical Hardware




Type 2 — Hosted

Virtual Machines


Ubuntu / Windows


VirtualBox / VMware Workstation


Host OS (Windows / Linux)


Physical Hardware

You install VMware ESXi directly onto the server, then create:

- VM1: Ubuntu, 4 vCPUs, 8 GB RAM

- VM2: Ubuntu, 4 vCPUs, 16 GB RAM

- VM3: Windows, 4 vCPUs, 16 GB RAM

ESXi schedules the VMs on the physical hardware.


Scenario B: Hosted virtualization


You install Windows 11 on the physical server, then install VirtualBox and create those same three VMs.


Windows 11 continues running its own desktop applications, services, antivirus, etc. VirtualBox uses the resources available through the host OS to operate the VMs.


Both arrangements can use hardware-assisted virtualization, so hosted does not mean every VM instruction must be interpreted by Windows.


---------------------------------------------------------------------------------------------------------------------------


k8 vs docker in terms container management 



The easiest way to understand the difference:

- Docker: Creates, runs, and manages containers on an individual machine.


- Kubernetes (K8s): Orchestrates containers across one or many machines, handling scheduling, scaling, recovery, networking, and deployments.



Think of Docker as a container runtime and management platform, while Kubernetes is a container orchestration platform.





Feature Docker Kubernetes (K8s)

Run containers Yes Yes, through a container runtime

Build container images Yes No

Manage containers on one VM Yes Yes

Manage multiple VMs as a cluster Not with standalone Docker Yes

Automatically restart crashed containers Yes, with restart policies Yes, via controllers

Automatically move workloads when a VM fails No Yes, if capacity is available

Load balancing Basic networking and port publishing Services and traffic distribution

Auto-scaling Not built into standalone Docker Yes, with autoscaling components

Rolling deployments Limited with standalone Docker Built in

Service discovery Docker networks Kubernetes Services and DNS


---------------------------------------------------------------------------------------------------------------------------

Docker manages containers services in each vm independently 


# VM 1

docker run -d --name api my-api:1.0


# VM 2

docker run -d --name api my-api:1.0


# VM 3

docker run -d --name api my-api:1.0


If VM 2 crashes, Docker on VM 2 cannot restart its application because the whole VM is unavailable.


You would need to manually recover it or use external automation.



---------------------------------------------------------------------------------------------------------------------------



Kubernetes Control Plane


Scheduling • Desired State • Recovery • Scaling


Worker VM 1

API Pod


containerd


Linux Kernel




Worker VM 2

API Pod


containerd


Linux Kernel




Worker VM 3

API Pod


containerd


Linux Kernel



---------------------------------------------------------------------------------------------------------------------------

No. Modern Kubernetes does not require Docker Engine.


It needs a compatible container runtime, typically containerd


Kubernetes Control Plane

          |

       kubelet

          |

    CRI Interface

          |

       containerd

          |

         runc

          |

    Linux Kernel

ubernetes introduces Pods

This is another important distinction.



Pod: Groups one or more containers together.



containerd: Container runtime that manages container execution on the worker node.




kubelet: Kubernetes agent running on the worker node, responsible for ensuring its assigned Pods and containers are running.




Linux kernel: Executes container processes and provides isolation.



Pod: my-api

|

|-- Container A: Go API

|      listens on port 8080

|

|-- Container B: Log collector

|      collects application logs

|

|-- Shared network

|-- Shared volumes (if configured)




Kubernetes Cluster

│

├── Control Plane (VM 1)

│   ├── API Server

│   ├── Scheduler

│   ├── Controller Manager

│   └── etcd

│

└── Worker Node (Ubuntu VM 2)

    │

    ├── kubelet

    │   └── Communicates with Control Plane

    │

    ├── containerd

    │   └── Creates and manages containers

    │

    ├── Pod: my-api

    │   ├── Container A: Go API (port 8080)

    │   ├── Container B: Log Collector

    │   ├── Shared Network (same Pod IP)

    │   └── Shared Volumes (if configured)

    │

    └── Ubuntu Linux Kernel

        └── Executes container processes

Control Plane

     │

     ▼

   kubelet

     │

     ▼

  containerd

     │

     ▼

    Pod

     │

     ├── Go API Container

     └── Log Collector Container

             │

             ▼

      Ubuntu Linux Kernel

             │

             ▼

       VM Virtual Hardware

             │

             ▼

          Hypervisor

             │

             ▼

      Physical Hardware

  

  

------------------------------


Docker Compose and Kubernetes both use YAML files, but their YAML structure, purpose, and configuration are different.

- Docker Compose: Uses compose.yaml or docker-compose.yml to define and run containers, typically on one machine.

- Kubernetes: Uses YAML manifests to define Pods, Deployments, Services, and other resources across a Kubernetes cluster.

Let's use your Go API + Log Collector example.

1. Docker Compose YAML

services:

  api:

    image: my-go-api:1.0

    ports:

      - "8080:8080"

    volumes:

      - logs:/var/log/app

    restart: always


  log-collector:

    image: my-log-collector:1.0

    volumes:

      - logs:/var/log/app

    restart: always


volumes:

  logs:



Run it with:

docker compose up -d



Docker Compose creates two containers on the same Docker host. They share the logs volume but normally have separate IP addresses.

2. Kubernetes YAML

apiVersion: apps/v1

kind: Deployment

metadata:

  name: my-api

spec:

  replicas: 2

  selector:

    matchLabels:

      app: my-api

  template:

    metadata:

      labels:

        app: my-api

    spec:

      containers:

        - name: api

          image: my-go-api:1.0

          ports:

            - containerPort: 8080

          volumeMounts:

            - name: logs

              mountPath: /var/log/app


        - name: log-collector

          image: my-log-collector:1.0

          volumeMounts:

            - name: logs

              mountPath: /var/log/app


      volumes:

        - name: logs

          emptyDir: {}



Deploy it with:

kubectl apply -f deployment.yaml



Kubernetes creates 2 Pods, and each Pod contains:

Kubernetes Deployment (replicas: 2)

│

├── Pod 1

│   ├── Go API container

│   ├── Log collector container

│   └── Shared logs volume

│

└── Pod 2

    ├── Go API container

    ├── Log collector container

    └── Shared logs volume

! You install Kubernetes control-plane components on VM1 and Kubernetes worker-node components on VM2. Then you can deploy applications from VM1, and Kubernetes can run them on VM2.

Architecture

Physical Server / Hypervisor

│

├── VM1: Kubernetes Control Plane (Ubuntu)

│   ├── API Server

│   ├── Scheduler

│   ├── Controller Manager

│   ├── etcd

│   └── kubectl (management CLI)

│

└── VM2: Kubernetes Worker (Ubuntu)

    ├── kubelet

    ├── containerd

    │

    └── Pod: my-api

        ├── Go API Container

        └── Log Collector Container

---------------------------------------------------------------------------------------


KVM deployment how it works 



How deployment works

Step 1 — Install Kubernetes


On VM1, initialize the Kubernetes control plane:

sudo kubeadm init



On VM2, install the necessary worker components and join the cluster using the command generated during initialization:

sudo kubeadm join <VM1-IP>:6443 --token ... \

  --discovery-token-ca-cert-hash sha256:...



These are simplified commands. Both machines need prerequisites such as a configured container runtime, kubelet, networking, and compatible Kubernetes versions.


Step 2 — Deploy from VM1


On VM1:

kubectl apply -f deployment.yaml



Step 3 — Kubernetes schedules the application

VM1: Control Plane

    │

    ├── Receives deployment.yaml

    ├── Scheduler selects VM2

    │

    ▼

VM2: Worker Node

    │

    ├── kubelet receives Pod assignment

    ├── containerd pulls container images

    └── Starts Go API + Log Collector



Step 4 — Verify the deployment

kubectl get nodes

kubectl get pods -o wide



The second command shows which worker node is running each Pod.

One important clarification

Kubernetes doesn't automatically install itself on VM2 from VM1.


You must first install and configure the Kubernetes worker components on VM2 and join it to the cluster.


After that, the control plane can schedule application Pods onto VM2 automatically.


Also, the control plane doesn't physically push your application binaries to VM2. The worker's container runtime normally pulls the required container images from a registry.


So the distinction is:

- VM1: Decides where and what should run.

- VM2: Actually runs the application containers.



----------------------------------------------------------------


ubernetes doesn't interactively ask you to add worker VMs. Instead, when you initialize the control plane using kubeadm, it generates a kubeadm join command that you run on each worker VM.

Step 1 — VM1: Initialize control plane

sudo kubeadm init



After successful initialization, Kubernetes prints something similar to:

kubeadm join 192.168.1.10:6443 \

  --token abcdef.1234567890abcdef \

  --discovery-token-ca-cert-hash sha256:xxxx



Save this command.

Step 2 — VM2: Join as worker

First, VM2 must have the required Kubernetes components (kubeadm, kubelet, and a container runtime such as containerd) installed and configured.


Then execute the generated command on VM2:

sudo kubeadm join 192.168.1.10:6443 \

  --token abcdef.1234567890abcdef \

  --discovery-token-ca-cert-hash sha256:xxxx



VM2 registers itself with the control plane.

Step 3 — VM1: Verify workers

kubectl get nodes



Example output:

NAME   STATUS   ROLES           AGE   VERSION

vm1    Ready    control-plane   10m   v1.xx.x

vm2    Ready    <none>          2m    v1.xx.x



You'll also need to install a compatible CNI networking plugin before nodes and Pods can operate normally.

Step 4 — Add more workers later

You can add VM3, VM4, or VM5 using the same process.

Kubernetes Cluster

│

├── VM1: Control Plane

│

├── VM2: Worker

│   └── Pods

│

├── VM3: Worker

│   └── Pods

│

└── VM4: Worker

    └── Pods



If your original join token expires, generate a new join command on VM1:

sudo kubeadm token create --print-join-command


K8s is simply an abbreviation for Kubernetes.

- K = First letter

- 8 = Eight letters between K and s (ubernetes)

- s = Last letter



Kubernetes (K8s) is a real software platform, just like Docker and Hyper-V,


opensource 

Kubernetes (K8s) is a real software platform, just like Docker and Hyper-V,




Yes, exactly! Bare-metal Kubernetes means running Kubernetes directly on physical servers instead of virtual machines.




Yes, exactly! Bare-metal Kubernetes means running Kubernetes directly on physical servers instead of virtual machines.


The Kubernetes architecture and functionality are essentially the same. The main difference is the infrastructure underneath.

1. Kubernetes on VMs

Physical Server

    │

    └── Hypervisor (Proxmox / VMware)

         │

         ├── VM1: Ubuntu + K8s Control Plane

         │

         ├── VM2: Ubuntu + K8s Worker

         │       ├── kubelet

         │       ├── containerd

         │       └── Pods

         │

         └── VM3: Ubuntu + K8s Worker

                 ├── kubelet

                 ├── containerd

                 └── Pods



2. Bare-metal Kubernetes

Physical Servers

    │

    ├── Server 1: Ubuntu + K8s Control Plane

    │

    ├── Server 2: Ubuntu + K8s Worker

    │       ├── kubelet

    │       ├── containerd

    │       └── Pods

    │

    └── Server 3: Ubuntu + K8s Worker

            ├── kubelet

            ├── containerd

            └── Pods


------------------------------------------------------

 production Kubernetes, separating the control plane and worker nodes is generally good practice.

 In Kubernetes, a node usually means a VM or a physical machine.


Kubernetes Cluster

│

├── VM1: Control Plane

├── VM2: Control Plane

├── VM3: Control Plane

│

├── VM4: Worker

│   └── Application Pods

│

├── VM5: Worker

│   └── Application Pods

│

└── VM6: Worker

    └── Application Pods




Kubernetes Cluster

│

└── Worker Node (VM / Machine)

    │

    └── Pod: my-app

        │

        ├── Container A: Main App

        │      └── e.g. Go API on port 8080

        │

        ├── Container B: Helper

        │      └── e.g. Log collector

        │

        ├── Container C: Helper

        │      └── e.g. Metrics exporter

        │

        ├── Shared Network

        │      └── same Pod IP, talk via localhost

        │

        └── Shared Volumes

               └── if configured

   

   

   

Kubernetes Cluster

│

├── Control Plane (VM1)

│   ├── API Server         → Entry point for Kubernetes API

│   ├── Scheduler          → Decides which node runs a Pod

│   ├── Controller Manager → Maintains desired state

│   └── etcd               → Stores cluster configuration/state

│

└── Worker Node (VM2)

    │

    ├── kubelet            → Communicates with API Server

    │                        Ensures assigned Pods are running

    │

    ├── containerd         → Creates and manages containers

    │                        using a runtime such as runc

    │

    └── Pod: my-app        → Groups related containers

        ├── Container A: Go API

        ├── Container B: Log Collector

        ├── Shared Network

        └── Shared Volumes (if configured

A Kubernetes Pod is somewhat similar to a group of containers defined in a Docker Compose YAML file.


But there's one important distinction:


Docker Compose manages a group of containers, while a Kubernetes Pod is a runtime unit containing one or more tightly coupled containers.

1. Docker Compose

docker-compose.yaml

│

├── Go API Container

├── MySQL Container

└── Redis Container



All three containers are managed together by Docker Compose, but each typically has its own IP address and lifecycle.

2. Kubernetes

deployment.yaml

│

└── Deployment

    │

    ├── Pod 1

    │   ├── Go API Container

    │   └── Log Collector Container

    │

    ├── Pod 2

    │   ├── Go API Container

    │   └── Log Collector Container

    │

    └── Pod 3

        ├── Go API Container

        └── Log Collector Container



Each Pod has its own IP address, and the containers inside that Pod share its network.


Pod: my-app

│

├── IP: 10.244.1.5

│

├── Container A: Go API

│   └── 10.244.1.5:8080

│

└── Container B: Log Collector

    └── 10.244.1.5:9090



How communication works

Inside the same Pod:

curl http://localhost:8080  # Go API

curl http://localhost:9090  # Log Collector



From another Pod:

curl http://10.244.1.5:8080  # Go API

curl http://10.244.1.5:9090  # Log Collector



For docker compose each container must have its own IP in bridge mode




By default, Docker assigns each container its own IP address on a Docker bridge network.


For example:

Ubuntu VM (192.168.1.10)

│

├── Docker Bridge Network: 172.18.0.0/16

│   │

│   ├── Container A: Go API

│   │   ├── IP: 172.18.0.2

│   │   └── Port: 8080

│   │

│   └── Container B: Log Collector

│       ├── IP: 172.18.0.3

│       └── Port: 9090

│

└── Linux Kernel




you still generally need a load balancer in front of Kubernetes if you want to expose your application to external users. But Kubernetes already provides internal load balancing, so you don't necessarily need HAProxy or Nginx for every application.


Also, one correction: Kubernetes doesn't require 3 VMs. You can run Kubernetes on 1 or 2 VMs. Three nodes become particularly useful when you want a highly available control plane using etcd quorum.

1. Example: Three VMs running Kubernetes

               External Clients

                      |

                      v

             External Load Balancer

                 (HAProxy)

                      |

                      v

            Kubernetes Cluster

                      |

        +-------------+-------------+

        |             |             |

       VM1           VM2           VM3

    Control Plane  Control Plane  Control Plane

      + Worker       + Worker       + Worker

        |             |             |

      Pod A         Pod B         Pod C

      Go API        Go API        Go API

      :8080         :8080         :8080

  

  

Recommended high avaibility 




Kubernetes Cluster

│

├── Server 1

│   └── VM1: Control Plane + Worker

│       └── Go API Pod 1

│

├── Server 2

│   └── VM2: Control Plane + Worker

│       └── Go API Pod 2

│

└── Server 3

    └── VM3: Control Plane + Worker

        └── Go API Pod 3



  ONE Kubernetes Cluster

                       |

         +-------------+-------------+

         |             |             |

    Control Plane  Control Plane  Control Plane

         1             2             3

         |             |             |

         +-------------+-------------+

                       |

             Shared Cluster State

              (3-member etcd)

                       |

         +-------------+-------------+

         |             |             |

      Worker 1      Worker 2      Worker 3

         |             |             |

      Go API 1      Go API 2      Go API 3

Thursday, 29 October 2026

Character space vs storage space

 Storage space — how many distinct bit patterns a fixed amount of memory can hold:

2^bits

A byte = 2⁸ = 256 patterns (0–255). A 5-byte field = 2⁴⁰ ≈ 1.1 trillion patterns. This depends only on the memory, not on what you store.

Character space — how many distinct strings a format can produce:

L^n        L = alphabet size, n = characters

5 chars from a 34-symbol alphabet = 34⁵ = 45,435,424 strings. This depends only on the format, not on the memory.

They're equal only when the alphabet is a power of 2

FormatLnCharacter spaceStorage space (1 byte/char)Fraction used
binary28256 = 2⁸2⁸ = 2561 (no waste)
hex16516⁵ = 2²⁰2⁴⁰1/1,048,576
base-6464564⁵ = 2³⁰2⁴⁰1/1,024
decimal10510⁵ ≈ 2¹⁶.⁶2⁴⁰~1/11,000,000
base-34 (SN)34534⁵ ≈ 2²⁵.⁴2⁴⁰1/24,200



character space intheory is 36 0123456789abcde..z
Theoretically available alphanumerics36 (0-9, A-Z)

Friday, 16 October 2026

Network Ips, docker range

 /24 what does it mean?

first 24 bits of the IP address belong to the network portion, leaving the remaining 8 bits for individual devices (hosts).

so there will be total of 256 ips for 8 bits 
because 8 bits equals 256 because each bit has 2 possible states
2^8 = 256
1. The Total Count (256 Possibilities)
An 8-bit system has 8 binary slots, and each slot can be either a 0 or a 1. To find the total number of unique combinations, you calculate 2⁸, which equals 256. This means an 8-bit number can represent 256 distinct values.
2. The Maximum Value (255)
Because computers are digital systems, they start counting from 0 rather than 1.
  • The lowest possible value is all zeros: 00000000 (which equals 0).
  • The highest possible value is all ones: 11111111 (which equals 255).
If you count all the numbers from 0 to 255, you get exactly 256 total numbers.
  • The lowest possible 8-bit number is 00000000 (which is 0).
  • The highest possible 8-bit number is 11111111 (which is \(128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255)

 addresses in a network  =  2^(32 − N)

   /24  →  2^(32−24)  =  2^8  =  256      ✓ matches your count
   /28  →  2^(32−28)  =  2^4  =   16

Why it splits into 16

Carving a /24 into /28s means taking 4 more bits away from the host part:

   169.254.6.0/24    11111111.11111111.11111111 . 00000000
                                               └── 8 host bits ──┘
                                                    2^8 = 256 addresses

   169.254.6.0/28    11111111.11111111.11111111 . 1111 . 0000
                                               └─4─┘   └─4──┘
                                          new subnet    host bits
                                          bits: 2^4     = 2^4 = 16
                                              = 16        addresses
                                              subnets     each

asdad
"bip": "169.254.4.1/28",
"default-address-pools": [ { "base": "169.254.6.0/24", "size": 28 } ]

Two separate knobs, and they're easy to conflate:

KeyMeaning
basethe total space Docker may carve up
sizethe prefix length of each network it creates — not the number of networks

So size: 28 means every auto-created network is a /28. The base then divides evenly:

169.254.6.0/24  ── 256 addresses, sliced into /28 ──
├── 169.254.6.0/28      ← network 1
├── 169.254.6.16/28     ← network 2
├── 169.254.6.32/28     ← network 3
│   ⋯
└── 169.254.6.240/28    ← network 16


asdsad
sa

16 is the block size. Not all 16 are assignable:

   169.254.9.0/28
   ├── .0    network address    ← not assignable
   ├── .1    gateway            ← Docker takes it
   ├── .2 … .14   13 container slots
   └── .15   broadcast          ← not assignable

   block = 16  ·  usable hosts = 14  ·  after the gateway = 13


asdsadasd Docker colloision

ip route get 172.17.0.5

   BEFORE                                AFTER  (a stack comes up on 172.17.0.0/16)
   ─────────────────────────────         ────────────────────────────────────────
   default         via 172.19.11.1       172.17.0.0/16   dev docker0   ← NEW, /16 > /0
   169.254.0.0/16  enp0s31f6             172.19.11.0/24  enp0s31f6
   172.19.11.0/24  enp0s31f6             169.254.0.0/16  enp0s31f6
                                          default         via 172.19.11.1

   packet ─→ gateway ─→ LAN ─→ 172.17.0.5        packet ─→ docker0 ─→ ARP ─→ nobody
              ✓ SYN arrives                                  ✗ dies. 172.17.0.5
                                                               never saw a packet.


asdadad cooliosn B 


 Docker created a network on 172.19.11.0/28   ← contains 172.19.11.1

   main table                            local table = "addresses I answer for"
   ───────────────────────────           ──────────────────────────────────────
   172.19.11.0/28  br-18191aa6  ← NEW    local 172.19.11.161  dev enp0s31f6
   172.19.11.0/24  enp0s31f6             local 172.19.11.1    dev lo       ← NEW
   default         via 172.19.11.1
                                         the host now thinks the GATEWAY is itself

   packet for 8.8.8.8 ─→ default via 172.19.11.1 ─→ look up .1 ─→ "that's me"
                                                       └─→ delivered to lo ─→ ✗

why colliosnn 

 ── inside the host (nothing on the wire yet) ──      ── on the wire ──
   ───────────────────────────────────────────────      ─────────────────

   1.  app: send() to 8.8.8.8
   2.  routing table: which interface?
         8.8.8.8 is not in 172.19.11.0/24
         → default route → next hop 172.19.11.1, dev enp0s31f6
   3.  routing table AGAIN: how do I reach 172.19.11.1?
         it IS in my /24 → on-link → ARP for it
   4.  ARP: who has 172.19.11.1?  →  00:09:0f:09:6f:1a
   5.  build the ethernet frame:
         dst MAC = 00:09:0f:09:6f:1a   ← the gateway
         dst IP  = 8.8.8.8             ← unchanged
                                                     ────→ gateway ──→ … ──→ 8.8.8.8

asdadadsadasdsad ssh colliosn 

 you: 172.19.11.160                    host: 172.19.11.161  (sshd :22)
   ──────────────────                    ──────────────────────────────

   1.  SYN ───────────────────────────→  arrives on enp0s31f6
                                          ← no routing decision needed.
                                            A packet that arrives, arrives.
   2.                                       kernel accepts. sshd replies.
   3.                                       route lookup for 172.19.11.160
                                            ← ★ THIS is where it dies
   4.  SYN-ACK ←─────────────────────────   (or doesn't)

three way ssh colliosn 
   ── A ── your address falls inside a range Docker claimed
   ─────────────────────────────────────────────────────────
   reply to 172.19.11.160  →  matches 172.19.11.0/24 → enp0s31f6   ✓
   reply to 172.19.11.5    →  matches 172.19.11.0/28 → br-xxxx     ✗
                              (Docker took .0/28; .5 is inside it)

   ── B ── you're off-subnet, and the gateway got shadowed
   ─────────────────────────────────────────────────────────
   reply to 10.20.30.40    →  off-link → default via 172.19.11.1
                              → lookup .1 → "local, dev lo" → loopback   ✗

   ── C ── rp_filter = 1 (strict)
   ─────────────────────────────────────────────────────────
   SYN arrives on enp0s31f6. Kernel checks: would I send *back* to
   this source via enp0s31f6?  A Docker route now says "no, via docker0"
   → packet dropped BEFORE sshd ever sees it.      ✗
   (this host is at rp_filter=2, so it's spared)


asdadasdasdsad host vs brdige(host dangerous)

docker0 — yes, with one trap that catches everyone

The trap is a naming collision: bridge is both a driver and the name of one specific network.

   driver "bridge"        ← the technology. docker0 AND every br-xxxx use it.
   network named "bridge" ← docker0 specifically. The default one.
   network_mode: bridge   ← attach to docker0.

So this lands you on docker0:

services:
  app:
    network_mode: bridge      # explicit

And this does not — it silently creates a user-defined bridge called <project>_default:

services:
  app:
    networks: [default]       # ← its own br-xxxx, NOT docker0

Compose's auto-created default network is a user-defined bridge. Every compose stack on this host proves it:

   docker network ls
   bridge                              ← docker0. linkdown. empty.
   umami_default                       ← user-defined, br-ba9f53a61dc4

That's why docker0 is linkdown here — nothing has ever asked for it.

you writeyou getgateway
network_mode: bridgedocker0bip
nothing / networks: [default]<project>_default (own br-xxxx)subnet + 1
an explicit networks: blockone br-xxxx per networksubnet + 1
network_mode: hostnonenone

User-defined is strictly better, which is why docker0 is effectively legacy:

  • DNS by name — on a user-defined network, db resolves to the container. On docker0 you need --link (deprecated) or hardcoded IPs. That's why umami-umami-1 can just say db in its connection string.
  • Isolation between networks — Docker inserts DOCKER-ISOLATION rules so certgen-net can't reach umami_default. The default bridge is more permissive.
  • Hot attach/detach without restarting containers.

2. network_mode: host — yes, genuinely dangerous

It removes the boundary entirely. The container doesn't get its own network namespace; it shares the host's.

   ── bridged ──                          ── host mode ──
   ┌─ container netns ─┐                  ┌────────────────────────┐
   │ eth0 169.254.9.3  │                  │  container process     │
   └────────┬──────────┘                  │  lives in the HOST's   │
            │ veth                        │  network namespace     │
   ┌────────▼──────────┐                  │                        │
   │ br-ba9f53a61dc4   │ ← boundary       │  binds :443, :22, :80  │
   └────────┬──────────┘                  │  reaches 127.0.0.1     │
            │ iptables NAT + isolation    │  sees the whole LAN    │
   ┌────────▼──────────┐                  └───────────┬────────────┘
   │ host 172.19.11.161│                              │
   └───────────────────┘                  no boundary. it IS the host.

sadsadad

mode        allocates an IP?          from where              can it take .161?
   ─────────   ───────────────────────   ─────────────────────   ─────────────────
   host        no                        —                       only if the host
                                                                 already has it
   bridge      yes                       169.254.x.0/28 (Docker)  no
   macvlan     yes                       your LAN subnet          yes — and that's
                                                                 a duplicate-IP bug


asdsad using host mode no longer uses bips, container just seperate themself by taking over real host ports

   HOST MODE × 3
   ─────────────

   ┌──────────────────────────────────────────────────────┐
   │  the host's single network namespace                 │
   │                                                      │
   │   enp0s31f6   172.19.11.160/24                       │
   │                                                      │
   │   :80    ← container A                               │
   │   :8080  ← container B                               │
   │   :9000  ← container C                               │
   │                                                      │
   │   no eth0 for any of them. no address. no veth.      │
   │   A, B and C reach each other on 127.0.0.1           │
   └──────────────────────────────────────────────────────┘

   from the LAN, all three answer to the SAME address:

     172.19.11.160:80     172.19.11.160:8080     172.19.11.160:9000
     └──────────────── one IP, three ports ────────────────┘

The port becomes the only thing that tells them apart

   BRIDGED × 3                          HOST MODE × 3
   ───────────                          ─────────────
   ┌─A─┐ ┌─B─┐ ┌─C─┐                    ┌──────────────────────┐
   │.2 │ │.3 │ │.4 │  ← own addresses   │  :80  :8080  :9000   │
   └─┬─┘ └─┬─┘ └─┬─┘                    │  ← only ports differ │
     └─────┼─────┘                      └──────────────────────┘
       br-xxxx (169.254.9.1)
           │
     host 172.19.11.161

asdasda

and no host to docker port mappings