A container looks like a small computer. It has a filesystem, a hostname, processes, users, network interfaces, and a package manager. Treating it as a tiny server is therefore an understandable mistake.

It is also the source of a surprising number of operational problems.

A container is better understood as one or more ordinary processes running on a host, given a particular view of the system and placed inside a set of resource and security boundaries. The container has not escaped the host. It is using the host kernel under controlled conditions.

A container does not remove operational decisions. It makes those decisions explicit—if the configuration is honest.

The boundary is the product

Container runtimes assemble several Linux mechanisms. Namespaces can give a process a private view of resources such as process IDs, mounts, and networking. Control groups can account for and constrain resources. Filesystem layers supply the image and writable container state.

The important idea is not the list of mechanisms. It is that isolation has dimensions.

A process can be isolated from another process’s filesystem view while still competing with it for CPU time. It can have its own network namespace while using the same host kernel. It can run as root inside a user namespace without necessarily having unrestricted host privileges—but that depends on the actual configuration.

“It runs in a container” is therefore not a complete security, performance, or reliability statement.

The image is a recipe, not the running state

An image should contain the application and the files required to start it. A container adds a writable layer when it runs, but that layer belongs to the container’s lifecycle.

If important data exists only there, replacing the container replaces the data. That is not Docker being careless; it is the deployment model doing exactly what it was asked to do.

Persistent state needs an explicit home:

  • a managed volume for data whose lifecycle must outlive a container;
  • a bind mount when a known host path must be visible inside the container;
  • an external service when state belongs outside the application host;
  • a temporary filesystem for data that should disappear.

Docker’s volume documentation makes the lifecycle distinction directly: volume contents exist outside an individual container and remain after that container is removed.

The practical test is simple: if this container is deleted and recreated from its image, what is lost?

A bind mount is deliberate coupling

Bind mounts are ideal for local development. Source code edited on the host can appear immediately inside a development container. That is how a containerised Astro development server can provide rapid feedback without rebuilding an image after every save.

The same feature creates a production dependency. A bind-mounted service relies on a particular host path, its permissions, its content, and its backup policy. Docker also notes that mounting over a non-empty container directory obscures the files already present there.

Use a bind mount when coupling to the host is the goal. Do not use one merely because it made the first experiment convenient.

For production static sites, baking the generated files into an immutable NGINX image produces a simpler promise: the image that passed testing contains the site that will be served.

Running is not ready

A process can exist before it can do useful work.

A database may be replaying logs. An API may be running migrations. A model server may still be loading weights. A web process may have opened its port before a dependency is reachable.

Docker Compose starts services in dependency order, but its documentation is explicit that “running” is not the same as “ready”. Where readiness matters, a dependency can use a health check and condition: service_healthy.

services:
  api:
    depends_on:
      database:
        condition: service_healthy

  database:
    image: postgres:18
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

A good health check tests the capability its dependent needs. “The process exists” is weak evidence that a database can accept a query or an API can complete a representative request.

Unlimited is still a resource policy

Putting a service in a container does not automatically give it a sensible CPU or memory allowance.

Docker documents that containers have no resource constraints by default. They can use as much CPU or memory as the host scheduler permits. That may be reasonable on a dedicated machine. On a shared host it means the real policy is “compete until something else suffers”.

Limits require measurement:

  • Too little memory can cause reclaim, swapping, or termination.
  • A hard CPU quota can turn a brief burst into periodic throttling.
  • No limit can allow one workload to damage unrelated services.
  • A limit copied from a laptop may be meaningless on the production host.

Set constraints from observed workload and failure requirements, then monitor what happens near the boundary. A limit without telemetry is merely a different kind of guess.

The main process has a job beyond the application

The container remains alive while its main process remains alive. That process also receives termination signals and may need to reap child processes.

This is why “one process per container” is an imprecise rule. A web server can legitimately have multiple worker processes. The more useful rule is one area of responsibility per container, with a clear main process that handles lifecycle correctly.

Docker recommends separating areas of concern and provides an --init option for applications that do not manage child processes properly. Adding a complete init system to every image is rarely the first answer.

Test shutdown as deliberately as startup:

  1. Send the normal stop signal.
  2. Confirm the application stops accepting new work.
  3. Allow in-flight work to finish or fail predictably.
  4. Confirm the process exits within the configured grace period.
  5. Check that the next start does not depend on abandoned temporary state.

Restarts are recovery policy, not a cure

A restart policy answers what the runtime should do after a process exits. It does not explain why the process exited or prove that the recovered service is correct.

unless-stopped may be a sensible policy for a long-running service on a small host. on-failure with a bounded retry count may be better for a job where a permanent configuration error should remain visible.

An unconditional restart can turn a clear failure into an endless loop that fills logs and repeatedly hits a dependency. Pair the policy with useful exit codes, health information, and an alerting path.

A practical container contract

Before calling a service production-ready, be able to answer:

  • Which exact image and configuration will run?
  • What state must survive replacement, and where is it stored?
  • Which host paths or devices does the service depend on?
  • What proves the service is ready?
  • What CPU and memory behaviour is acceptable?
  • Which signal stops it, and how long should shutdown take?
  • What should restart automatically, and what should remain failed?
  • How will logs, health, and resource pressure be observed?
  • Can the image be rebuilt, tested, and promoted without editing the server?

That list is less glamorous than a working docker compose up. It is also the difference between a convenient package and an operable service.

The useful insight

Thinking “tiny server” encourages configuration from inside the box. Thinking “process with boundaries” directs attention to the contracts that matter: host, storage, resources, lifecycle, and dependencies.

Containers become simpler once the boundary is visible. The application is packaged; the operating model still needs to be designed.

Sources and further reading

Commercial disclosure

This article contains no affiliate links and was produced independently.

About the author

Samuel Brocksopp

Samuel writes about systems, measurement, infrastructure, and the places where technical decisions meet real-world constraints.

Editorial approach