I'm planning to run more than 40 React applications on a single reasonably powerful server. Each app needs its own isolated runtime and deployment lifecycle, so combining everything into one shared process is not an option. I'm trying to keep memory usage and operational complexity low while still being able to update apps independently. High availability is not required, and I can tolerate occasional downtime to avoid unnecessary cost.
Would plain Docker with Compose be more efficient and practical than k3s on a single node, or would Kubernetes resource limits, health checks, and deployment tooling justify its additional overhead? Are there ways to reduce per-container memory usage, such as lean base images, shared image layers, or careful resource limits? I'd also like to know whether 40 or more containers is a realistic workload for one server and whether the answer changes if these are purely static React builds rather than applications requiring a Node.js or backend process.
4 Answers
If every app really does need its own runtime, 40 containers is not an unusual number for a properly sized Linux server. Use multi-stage builds with Alpine or distroless runtime images where appropriate, reuse common image layers, and measure each service instead of assuming the container layer is the main memory cost. The application runtime, caches, and data handling usually consume much more memory than the container itself.
Set limits based on actual usage and monitor the host, containers, and runtime garbage collectors. A similar setup has run around 40–50 internal services comfortably, and larger test installations can run well over 100 containers when traffic is modest.
For a single server, Docker Compose is usually the better starting point. Containers themselves add very little overhead beyond the processes they run, while k3s adds components such as the control plane, kubelet, and datastore. That extra memory may be worthwhile for multi-node scheduling, rollouts, health checks, and a Kubernetes-based deployment workflow, but it does not provide much benefit if you know this will remain one machine.
Use a reverse proxy, keep each app in its own service, and give the containers sensible memory and CPU limits so one application cannot consume the whole host. Compose can manage dozens of services without a practical container-count problem.
The biggest question is whether these are actually running React applications. A production React build is normally just HTML, JavaScript, CSS, and other static assets. It can be served directly by one Nginx, Caddy, or Traefik instance from separate directories, with no Node process or container per app. That would be dramatically lighter than running 40 application servers.
If an app has its own backend or genuinely needs a separate runtime, containerize it. But if these are frontend-only builds, the isolation requirement may be unnecessary at runtime; separate build and deployment pipelines can still keep them independent.
k3s can work perfectly well on one node, especially if you expect to add nodes later or want Kubernetes-native deployments, Helm, health checks, rollbacks, and GitOps. However, it is not automatically more efficient. On a single host without high-availability requirements, Compose is simpler and generally uses less memory. I would start with Compose and migrate only when its deployment and operational workflow becomes a real limitation.
Whichever option you choose, avoid treating frontend applications as microservices by default. First determine what must be isolated, what can be static, and what traffic and uptime requirements you actually have.

That distinction is useful. I’m still investigating whether every app needs its own backend process or whether some can be reduced to static builds.