Is your feature request related to a problem? Please describe.
The current production image (docker/dockerfile/Dockerfile) is an all-in-one container that bundles the Next.js app, PostgreSQL, tusd, and nginx in a single image. This causes two separate problems:
- Not compatible with hardened Kubernetes environments. The image is designed to run as root by default, with nginx binding to port 80 and PostgreSQL's
initdb/postgres process needing a resolvable UID/GID via /etc/passwd. Clusters that enforce Pod Security Admission in restricted mode (runAsNonRoot: true, arbitrary runAsUser, no privilege escalation) reject the image outright, or require the Helm chart's securityContext to be relaxed, which isn't acceptable in many organizations' security policies.
- Unnecessarily large attack surface. Bundling PostgreSQL, tusd, and nginx binaries/packages inside the app image means the app image ships ~300 extra packages (and their CVEs) that have nothing to do with the Next.js application itself, are already available as maintained upstream images, and make the app image slower to rebuild/rescan on every dependency bump.
Describe the solution you'd like
Replace the monolithic image with a slim, single-purpose app image, and let tusd/nginx/PostgreSQL be regular sidecar/external services wired together via Docker Compose (and, in Kubernetes, via the Helm chart):
Dockerfile containing only the Node.js/Next.js dashboard app, single-stage, minimal apt footprint, running as a fixed non-root UID:GID (e.g. 1001:1001) by default via USER 1001:1001. No custom entrypoint script at all — the app already runs its own migrations/init on boot (init() in src/utils/init), so there's nothing left for an entrypoint to orchestrate. This also removes the /etc/passwd self-registration hack that only existed to satisfy PostgreSQL's getpwuid() requirement — moot once PostgreSQL isn't embedded.
nginx.conf proxying to the app and tusd by service name instead of 127.0.0.1, run via the standard upstream nginx image.
compose.yaml (the reference/example compose file) wiring together portabase (the app), tusd (tusproject/tusd, official image), postgres, and nginx — all as independent services/images.
- No embedded/fallback PostgreSQL and no entrypoint logic to install or manage one locally: if a user doesn't provide
DATABASE_URL, that's a configuration error, not something the container should paper over by spinning up its own local PostgreSQL. PostgreSQL is just another compose service like any other.
- Update the Helm chart to:
- Set
securityContext/podSecurityContext defaults compatible with Pod Security Admission restricted (runAsNonRoot: true, runAsUser/runAsGroup: 1001, allowPrivilegeEscalation: false, readOnlyRootFilesystem where feasible, capabilities.drop: [ALL]) for the portabase and tusd containers.
- Deploy tusd as its own Deployment/container (using the upstream image) instead of a process inside the app pod, matching the new compose topology. nginx has no Kubernetes equivalent in the chart — routing is handled by an Ingress resource instead.
Describe alternatives you've considered
There isn't really an alternative approach here so much as an industry convergence: this is the standard shape for hardened container images (see Docker Hardened Images and most other minimalist/hardened image initiatives) — small, single-purpose, non-root by default. Following it also means users don't have to rebuild and audit their own non-hardened images just to run Portabase in a restricted environment; they can rely on the upstream image directly.
The only real alternative would be to keep the current all-in-one Dockerfile as-is and try to make it run non-root in place. Given the number of packages and processes it bundles (PostgreSQL, tusd, nginx, the app), that's significantly more complex to get right and only adds maintenance burden rather than removing it.
Is your feature request related to a problem? Please describe.
The current production image (
docker/dockerfile/Dockerfile) is an all-in-one container that bundles the Next.js app, PostgreSQL, tusd, and nginx in a single image. This causes two separate problems:initdb/postgresprocess needing a resolvable UID/GID via/etc/passwd. Clusters that enforce Pod Security Admission inrestrictedmode (runAsNonRoot: true, arbitraryrunAsUser, no privilege escalation) reject the image outright, or require the Helm chart'ssecurityContextto be relaxed, which isn't acceptable in many organizations' security policies.Describe the solution you'd like
Replace the monolithic image with a slim, single-purpose app image, and let tusd/nginx/PostgreSQL be regular sidecar/external services wired together via Docker Compose (and, in Kubernetes, via the Helm chart):
Dockerfilecontaining only the Node.js/Next.js dashboard app, single-stage, minimalaptfootprint, running as a fixed non-root UID:GID (e.g.1001:1001) by default viaUSER 1001:1001. No custom entrypoint script at all — the app already runs its own migrations/init on boot (init()insrc/utils/init), so there's nothing left for an entrypoint to orchestrate. This also removes the/etc/passwdself-registration hack that only existed to satisfy PostgreSQL'sgetpwuid()requirement — moot once PostgreSQL isn't embedded.nginx.confproxying to the app and tusd by service name instead of127.0.0.1, run via the standard upstreamnginximage.compose.yaml(the reference/example compose file) wiring togetherportabase(the app),tusd(tusproject/tusd, official image),postgres, andnginx— all as independent services/images.DATABASE_URL, that's a configuration error, not something the container should paper over by spinning up its own local PostgreSQL. PostgreSQL is just another compose service like any other.securityContext/podSecurityContextdefaults compatible with Pod Security Admissionrestricted(runAsNonRoot: true,runAsUser/runAsGroup: 1001,allowPrivilegeEscalation: false,readOnlyRootFilesystemwhere feasible,capabilities.drop: [ALL]) for theportabaseandtusdcontainers.Describe alternatives you've considered
There isn't really an alternative approach here so much as an industry convergence: this is the standard shape for hardened container images (see Docker Hardened Images and most other minimalist/hardened image initiatives) — small, single-purpose, non-root by default. Following it also means users don't have to rebuild and audit their own non-hardened images just to run Portabase in a restricted environment; they can rely on the upstream image directly.
The only real alternative would be to keep the current all-in-one
Dockerfileas-is and try to make it run non-root in place. Given the number of packages and processes it bundles (PostgreSQL, tusd, nginx, the app), that's significantly more complex to get right and only adds maintenance burden rather than removing it.