Skip to main content
CleanStart

Knowledge Hub

CleanStart Security: Non-Root and Read-Only Filesystem

Last updated: April 2026

Overview

CleanStart images are hardened by default: they run as a non-root user with a read-only root filesystem. This eliminates entire classes of container escape and privilege escalation attacks. This guide explains what that means for your deployments and how to configure writable paths for your application.

Non-Root Execution

Default User

All CleanStart production images run as:

Field

Value

UID

65532

GID

65532

Username

clnstrt

Group

clngroup

This is enforced at both the image level (OCI User config) and in generated Kubernetes manifests:

securityContext:  runAsUser: 65532  runAsGroup: 65532  fsGroup: 65532  runAsNonRoot: true  allowPrivilegeEscalation: false  capabilities:    drop: ["ALL"]

Custom Users

If your application requires a specific UID/GID, define it in the spec's accounts section:

accounts:  - username: postgres    uid: 999    gid: 999

The generated image and Kubernetes manifests will use the specified user.

Migrating from Root-Based Images

If you are migrating from images that run as root (UID 0) or from Bitnami (UID 1001), you may need to fix permissions on existing persistent volumes:

initContainers:  - name: fix-permissions    image: gcr.io/clean-image-build/base:latest    securityContext:      runAsUser: 0    # initContainer runs as root to fix ownership    command: ["chown", "-R", "65532:65532", "/data"]    volumeMounts:      - name: data        mountPath: /data

Note: chown is not a cleanimg-init builtin. This initContainer uses the base image's chown binary directly, running as root to change ownership before the main container starts as UID 65532.

--- ## Read-Only Root Filesystem ### What It Means The container's root filesystem is mounted read-only. Your application cannot write to `/etc`, `/usr`, `/var`, or any other path unless you explicitly configure a writable volume. This prevents attackers from modifying binaries, injecting configuration, or writing malware to the filesystem even if they compromise the application process. ### How It's Enforced CleanStart sets the OCI image label:

dev.cleanstart.kubernetes.readOnlyRootFilesystem: "true"

Generated Kubernetes manifests include: ```yamlsecurityContext:  readOnlyRootFilesystem: true

Configuring Writable Paths

Applications need to write to certain directories (logs, temp files, caches, data). CleanStart supports two approaches:

Image-Level Writable Paths

Declare writable paths in your spec file. These directories are created during the build with correct ownership (matching the configured UID/GID):

deployment:  profile: production  writable_paths:    - /data    - /var/log    - /tmp

At runtime, these directories exist in the image but still require volume mounts to be writable under readOnlyRootFilesystem: true.

Kubernetes emptyDir Volumes

Generated Helm charts and manifests automatically mount emptyDir volumes for declared writable paths:

volumes:  - name: data    emptyDir:      sizeLimit: "1Gi"  - name: tmp    emptyDir:      sizeLimit: "64Mi"      medium: "Memory"    # tmpfs for /tmp volumeMounts:  - name: data    mountPath: /data  - name: tmp    mountPath: /tmp

For applications that need persistent storage, replace emptyDir with a PersistentVolumeClaim. Generated StatefulSet manifests do this automatically when persistence patterns are detected.

Common Writable Paths by Application

Application Type

Typical Writable Paths

Web servers

/tmp, /var/run, /var/cache

Databases

/data, /var/log, /tmp, /var/run

Message brokers

/data, /var/log, /tmp

Application servers

/tmp, /var/log, /var/run

Pod Security Standards

CleanStart images are compatible with Kubernetes Pod Security Standards at the Restricted level:

Requirement

CleanStart Default

Running as non-root

Yes (UID 65532)

runAsNonRoot: true

Yes

Privilege escalation disabled

Yes

All capabilities dropped

Yes

Read-only root filesystem

Yes

Seccomp profile

RuntimeDefault

shareProcessNamespace

true (enables kubectl debug)

You can deploy CleanStart images in namespaces with the strictest Pod Security Admission policies without modification.

Debugging with Read-Only Filesystem

Since the production filesystem is read-only and contains no shell, use kubectl debug with the debug variant image:

kubectl debug -it <pod-name> \  --image=gcr.io/clean-image-build/redis:8.2.2-debug

The debug container shares the pod's process namespace (via shareProcessNamespace: true) so you can inspect the production process, capture dumps, and run diagnostics. See Debug Guide for details.

Troubleshooting

"Permission denied" writing files -- The path is either on the read-only root filesystem or owned by a different UID. Add an emptyDir volume mount for that path, or verify the volume's fsGroup matches the container's GID.

Persistent volume has wrong ownership -- Use an initContainer running as root to chown the volume before the main container starts. See the migration example above.

Application expects to run as root -- CleanStart images enforce runAsNonRoot: true. If your application genuinely requires root (e.g., binding to port 80), either reconfigure it to use a high port or use NET_BIND_SERVICE capability (contact CleanStart support for guidance).

Pod rejected by admission controller -- CleanStart images comply with Restricted Pod Security Standards by default. If your admission controller is rejecting pods, check that you haven't added custom settings that violate the policy (e.g., adding capabilities back).