Running Containers on Small Hosts
Separate compilation from serving and review memory, filesystem, capabilities and rollback.
On this page
Separate the build host from the runtime host
A small application does not imply a small compiler workload. Dependency installation, compilation, type checking and page generation can run several processes with a larger peak memory demand than the final server. A constrained runtime host may serve the finished application reliably while failing to build it.
The practical pattern is to build a release in a suitable environment, verify it, and transfer an immutable result to the serving host. This note follows Fig’s explanation of process isolation. It describes a review method rather than asserting that a particular host has sufficient capacity.
Record the target operating system, architecture, runtime version and native-library requirements before building. A Linux x86-64 release is not interchangeable with an ARM release. A package containing native modules for glibc should not be assumed to run in an Alpine musl environment.
Observe a build failure before changing limits
A build ending with SIGKILL was forcibly terminated. Memory exhaustion is a plausible explanation on a small host, especially when swap is nearly full, but it is not the only possible cause. Correlate the failure with kernel messages, container limits and host memory observations.
free -h
ps -eo pid,ppid,%cpu,%mem,rss,etime,cmd --sort=-%cpu
sudo journalctl -k --since '15 minutes ago'Do not interpret an npm update notice as the cause of a killed compiler. Increasing a JavaScript heap limit can make host pressure worse if the machine does not have the capacity to support it. Conversely, a smaller heap may produce an explicit heap error rather than allowing a system-level kill. Both changes need an observed result.
Extra swap can make some builds complete, but paging adds latency and disk activity. It is a temporary resource trade-off, not evidence that the same machine is suitable for every build. Avoid performing repeated heavy builds on a host whose other services need predictable response times.
Keep the release small and explicit
A production package should contain the files needed to serve the application, not development caches, uploaded source material, editor state or secrets. Multi-stage container builds separate those concerns. A standalone framework output can further reduce the files needed at runtime.
Verify the package independently of the source checkout. Start it from its own directory, request the health route, inspect a page and load an article. A server that succeeds only while it can read the developer’s original content directory is not a complete release.
Include static assets and any local article files the server still reads. A successful compiler exit does not guarantee that a runtime file-tracing tool found every dynamically constructed path. The release procedure should record what is copied explicitly and test the resulting directory.
Bound the runtime without granting extra privilege
Run the service as an unprivileged user. A high application port does not require root. Drop unnecessary Linux capabilities and prevent privilege escalation through set-user-ID executables. Keep the container’s root filesystem read-only when the application does not need to modify its installed files.
Identify the writable paths it genuinely needs. A small temporary directory and a bounded cache are different from mounting the host root or the Docker socket. Temporary filesystem storage uses memory; include it in the container budget instead of treating it as free disk space.
A resource limit should leave room for the measured request workload and the runtime’s overhead. Observe resident memory and behavior under representative requests before lowering a limit. An attractive idle-memory number alone does not describe peak demand.
Expose one local application boundary
When a host reverse proxy terminates public HTTPS, publish the application port only on loopback. The proxy forwards the intended site traffic to that local service. A localhost binding reduces accidental direct exposure, but it does not replace host security or careful proxy configuration.
Keep health responses small and public-safe. They need not disclose a hostname, filesystem path, process environment or inventory of other services. A timestamp and a public service label are enough for a basic reachability check.
Bound request body size and timeouts at the proxy. For a read-only publication, write methods and file uploads are unnecessary. Rate controls should allow an ordinary page load and its assets while limiting repeated expensive dynamic requests.
Keep logs, startup and shutdown predictable
Configure log rotation before the first long-running deployment. Test the health check against the packaged server and allow a bounded startup period. A restart policy can recover a stopped process; it cannot fix a wrong image, a missing file or an unavailable dependency by repeatedly restarting it.
Allow the process a short grace period for shutdown. Request handling and background work need an explicit policy for termination. Preserve the previous release until the replacement has passed its checks.
Verify the rollback, not just the update
Record the release identifier, target platform, runtime requirements and image tag. A rollback means selecting the previous verified release and restarting the service with its compatible configuration. It should not depend on downloading changed dependencies or rebuilding an old source tree on the small host.
Consult Docker’s runtime resource constraints and tmpfs documentation for the controls behind the budget. The practical claim remains conditional: a verified package and measured runtime demand support a deployment decision for one specified host.