In Kubernetes a container is never scheduled on its own: it runs inside a pod, the smallest deployable unit, and the kubelet on a node asks a container runtime such as containerd to pull the image and start it.
The container itself is the same thing you build with Docker: an image plus a running process, isolated by the kernel. What Kubernetes adds is the wrapper around it, the pod, and the machinery that decides where that pod lands and what happens when it dies. This guide walks through that wrapper piece by piece, then ends on the question that saves the most money: do you need any of it?
Table of contents
- Container versus pod: the distinction that trips everyone
- How the kubelet and containerd run your container
- Multi-container pods: init containers and sidecars
- Resource requests and limits
- A pod YAML you can read
- When you do not need Kubernetes
- How this fits the rest of the stack
- FAQ
Container versus pod: the distinction that trips everyone
A container is one process (or process tree) started from an image, with its own filesystem view and resource limits. A pod is a group of one or more containers that are always scheduled together on the same node and share a few things: a network namespace, so they talk over localhost and share one IP address, and optionally storage volumes.
Most pods contain exactly one application container. That is the normal case and nothing is wrong with it. The multi-container pod is an exception you reach for when two processes are tightly coupled and must live and die together, such as an app and a log shipper. If they can scale independently, they belong in separate pods.
You almost never create a bare pod in production. You create a Deployment, which manages a ReplicaSet, which keeps N identical pods running. If a pod dies, the ReplicaSet creates a replacement. The pod is disposable by design, and any state you care about has to live outside it. The Kubernetes containers documentation covers the image and runtime concepts, and our container technology primer covers the layers underneath.
How the kubelet and containerd run your container
Every worker node runs an agent called the kubelet. When the scheduler assigns a pod to a node, the kubelet reads the pod spec and makes it real. It does not start containers itself. It speaks the Container Runtime Interface (CRI) to a runtime, and the runtime does the work.
The most common runtimes are containerd and CRI-O. Kubernetes stopped supporting Docker Engine directly as a runtime in version 1.24, when the dockershim component was removed. Images built with Docker still run fine, since they follow the OCI image standard, and containerd is the same engine that sits underneath Docker anyway. What changed is that the cluster talks to containerd directly.
- The scheduler picks a node and writes the assignment to the API server.
- The kubelet on that node sees the pod and asks the runtime to create a sandbox for it.
- The runtime pulls each image from the registry if it is not already cached.
- The runtime starts the containers, with the CPU and memory limits you specified.
- The kubelet runs the probes and reports status back to the API server.
This is why debugging a stuck pod is so often a runtime question in disguise: an image pull error, a missing secret, or a probe that fails and restarts the container in a loop. The commands for inspecting that are in our post on kubectl get pods.
Multi-container pods: init containers and sidecars
Kubernetes has three ways to put more than one container in a pod, and they are not interchangeable. Regular containers all start together and run for the life of the pod. Init containers run one at a time, in order, before any regular container starts, and each must finish successfully. Sidecars are helpers that run alongside the main container.
Init containers are for setup work that must finish first: waiting for a database to accept connections, running a migration, or fetching configuration into a shared volume. If one fails, the pod restarts it and the app never starts, which is a useful guard.
The sidecar pattern used to be a convention, a second regular container. Newer Kubernetes versions make it a first-class feature: you declare it in initContainers with restartPolicy: Always, and it starts before the app, keeps running throughout and shuts down after it. That fixes the old problem where a job’s sidecar kept the pod alive forever after the main task finished.
- Init container: run once, in order, before the app. Migrations, waiting, config fetch.
- Sidecar: runs alongside the app. Log shipping, proxies, certificate refresh.
- Second app container: only when the processes truly must scale together.
A warning from experience: every sidecar you add is another image to patch, another process consuming memory in every replica, and another thing that can crash the pod. Add one when it removes real complexity, not because a diagram had a box for it.
Resource requests and limits
Two numbers per container decide how your workload behaves under pressure. The request is what the scheduler reserves: a pod is placed only on a node with that much unclaimed CPU and memory. The limit is the ceiling the runtime enforces while it runs.
They behave differently. Exceed a CPU limit and the container is throttled, so it slows down but survives. Exceed a memory limit and the kernel kills the process with an out-of-memory error, and you see a restart with reason OOMKilled. Memory is a hard wall, CPU is a soft one.
Set requests from measured use, not from guesses. A request far above real use wastes node capacity because nothing else can be scheduled into the reserved space. A request far below it invites noisy neighbours and evictions. Requests and limits also determine the pod’s quality of service class: equal requests and limits give Guaranteed, requests below limits give Burstable, and none at all gives BestEffort, the first to be evicted when a node runs short.
For a JVM or Node service, leave headroom below the memory limit for non-heap use, or you get the maddening pattern of a service that looks healthy in profiles and is killed every few hours anyway.
A pod YAML you can read
Here is a pod that uses everything above: an init container that waits for a database, a main application container with requests, limits and a readiness probe, and a sidecar tailing logs from a shared volume. The pods documentation describes each field.
apiVersion: v1
kind: Pod
metadata:
name: web
labels:
app: web
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z db 5432; do sleep 2; done']
- name: log-shipper
image: fluent/fluent-bit:3.0
restartPolicy: Always # makes this a native sidecar
volumeMounts:
- name: logs
mountPath: /var/log/app
containers:
- name: app
image: registry.example.com/web:1.4.2
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: web-secrets
key: database-url
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 5
volumeMounts:
- name: logs
mountPath: /var/log/app
volumes:
- name: logs
emptyDir: {}
Read it top to bottom and it tells a story. The database check gates startup. The sidecar starts next and stays. The app declares what it needs, how to read its secret and how Kubernetes should know it is ready to receive traffic. The emptyDir volume is scratch space that lives as long as the pod, which is why persistent data never goes there.
When you do not need Kubernetes
Everything above is a large amount of machinery. It is worth it when you run many services, need fine control over placement and networking, have a team to operate the cluster, or must run the same platform across clouds. Outside that, it is an expensive way to run a container.
The signs you do not need it: one to a handful of services, a small team, traffic that a single service scales to with a larger plan, and no requirement for custom controllers or operators. In that world, the honest question is what you want from a container: a build, a route, environment variables, logs and a way to roll back. All of that can come from pushing a Dockerfile.
If you are weighing it, our list of Kubernetes alternatives covers the middle ground between a bare VM and a full cluster. Nothing about a container requires an orchestrator, and plenty of successful products ship on one service and one database for years. Adopt the cluster when a specific pain shows up, not because the job listings mention it.
How this fits the rest of the stack
If a container is all you need, skip the cluster and price the simple version. The RunxBuild hosting calculator shows what a Docker service and a managed Postgres or MySQL database cost on plans from a fraction of a vCPU up. RunxBuild does not run Kubernetes clusters. It does run your Dockerfile from a GitHub repository, with build logs, environment variables, runtime logs, metrics and rollback to the previous deploy, plus autoscaling between two plans you choose. The Docker services docs cover the setup, and the dashboard is where you start.
Useful related references:
- kubectl get pods explained
- Kubernetes alternatives
- Container technology explained
- Docker services on RunxBuild
FAQ
What is the difference between a container and a pod in Kubernetes?
A container is a single running process started from an image. A pod is the smallest deployable unit in Kubernetes and holds one or more containers that share a network namespace and optional volumes and are always scheduled on the same node.
How does Kubernetes run containers?
The kubelet on each node reads the pod spec, then asks a container runtime such as containerd through the Container Runtime Interface to pull images and start the containers. The kubelet then runs health probes and reports status to the API server.
What is a sidecar container?
A sidecar is a helper container that runs alongside the main application container in the same pod, sharing its network and volumes. Common uses are log shipping, proxies and certificate refresh. Newer Kubernetes lets you declare native sidecars as init containers with restartPolicy Always.
What happens when a container exceeds its memory limit?
The kernel kills the process with an out-of-memory error and Kubernetes restarts the container, showing the reason OOMKilled. Exceeding a CPU limit only throttles the container. Set memory limits with headroom above real peak use.
Do I need Kubernetes to run containers?
No. A single container can run on a VM, a container platform or a managed service that builds from a Dockerfile. Kubernetes pays off with many services, complex networking or a team able to operate a cluster, and is overkill for small deployments.