Skip to content

fix(container): /app/html created in image build layer causes fuse-overlayfs whiteout accumulation #318

Description

@alehostert

Static Badge

Summary

The /app/html directory is created during the image build in the Containerfile, which places it in a lower (read-only) layer of the container image. This causes fuse-overlayfs to generate whiteout files whenever content inside /app/html is deleted and recreated — a pattern that is common and frequent in WordPress installations (cache plugins, theme updates, plugin upgrades).

Over time, whiteout accumulation degrades filesystem performance progressively until the container becomes unresponsive. Full context and technical background in Ez issue #173.

Root cause

The relevant line in the Containerfile:

RUN ... \
    && mkdir -p /app/logs/cron /app/logs/nginx /app/conf/pki /app/.trash \
    && mkdir -m 777 -p /app/html \
    && chown -R nobody:nogroup /app

Because /app/html is created in a RUN instruction, it becomes part of a lower layer of the image. When a container starts and the user writes files under /app/html, those files land in the upper (writable) layer. But when a file or directory under /app/html is deleted, fuse-overlayfs must create a whiteout to shadow it from the lower layer — because the parent /app/html has last_layer_idx > 0.

This is confirmed by the needs_whiteout logic in src/overlay.rs, lines 1245–1253: a whiteout is created when the node or its immediate parent has origin in a lower layer.

The other directories created in the same RUN (logs, conf, .trash) are intentionally written during the build with configuration files and can remain in the image. /app/html is different — it is always empty in the image and only receives content at runtime, per user workload.

Proposed fix

Remove /app/html from the Containerfile and create it at container startup instead, so it always originates in the upper layer.

Containerfile — remove /app/html from the RUN instruction:

RUN ... \
    && mkdir -p /app/logs/cron /app/logs/nginx /app/conf/pki /app/.trash \
    && chown -R nobody:nogroup /app

Entrypoint or supervisord bootstrap — create /app/html on first run if it does not exist:

mkdir -m 777 -p /app/html
chown nobody:nogroup /app/html

With this change, /app/html is created in the upper layer of each individual container. Deletions inside it never require whiteouts — the needs_whiteout condition evaluates to false because both the node and its parent (/app/html) have last_layer_idx = 0.

Existing containers are not affected by the image change — /app/html already exists in their upper layer from prior writes. The fix applies to newly created containers going forward.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    approved

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions