sure: give Ruby a temporary directory it accepts

The first install-test run failed with both containers crash-looping
during boot:

  system temporary path is world-writable: /tmp
  /tmp is world-writable: /tmp
  . is not writable: /rails
  ArgumentError: could not find a temporary directory

raised from MiniMagick, which Active Storage loads while Rails boots.
Ruby's Dir.tmpdir skips a candidate that is world-writable without a
sticky bit, and a Kubernetes emptyDir is created mode 0777 with no sticky
bit - so with a read-only root filesystem there was no candidate left.

A tiny init container, the app's own image running as the same non-root
uid and with no capability added, now creates one private subdirectory
(mode 0700) inside each tmp emptyDir, and both containers run with
TMPDIR pointing at it. Creating an entry in a 0777 directory and chmod'ing
what one just created needs no privilege, so the hardening standard holds
unchanged.
This commit is contained in:
jklein authored and Justin Klein committed 2026-09-26 21:08:55 +00:00
1 parent 93fdb3eaea
commit a414af089d
3 files changed
+89 -6

No files matched your search

+12 -4
View File
@@ -36,10 +36,18 @@ sure:
# need a ReadWriteMany volume. Above 1 the extra pods normally stay Pending on
# "Multi-Attach error for volume" - see NOTES.txt.
replicas: 1
# Pod-level hardening, shared by both containers. The upstream image declares
# USER 1000:1000 (group and user "rails", created in the Dockerfile), so the
# chart needs no deviation from the repo hardening standard; fsGroup 1000
# makes the storage PVC writable for that uid.
# Pod-level hardening, shared by both containers and by the init container.
# The upstream image declares USER 1000:1000 (group and user "rails", created
# in the Dockerfile), so the chart needs no deviation from the repo hardening
# standard; fsGroup 1000 makes the storage PVC writable for that uid.
#
# readOnlyRootFilesystem: true holds, and the writable paths are emptyDirs:
# /rails/tmp, /rails/log and /tmp, one set per container. /tmp needs one extra
# step that is easy to mistake for decoration - a tiny init container creates
# a private subdirectory in it and the containers run with TMPDIR pointing
# there, because Ruby refuses a world-writable directory without a sticky bit
# as its temporary directory and an emptyDir is exactly that. Without it the
# app aborts during boot; the StatefulSet carries the measured evidence.
securityContext:
pod:
runAsNonRoot: true