Fix PUID/PGID collision issues by using gosu to run services with exact UID:GID. Changes include: - Added redis-start.sh and updated app-start.sh to load /etc/environment, determine PUID/PGID, and invoke gosu "$PUID:$PGID" to start Redis and the Next.js app (with verification and fallbacks). - Updated entrypoint.sh to persist PUID/PGID into /etc/environment, document the gosu approach, and adjust startup messaging. - Updated supervisord.conf to run the new startup wrappers as root (so they can use gosu) instead of running processes directly as specific users. - Dockerfile updated to install gosu and copy the redis-start.sh wrapper. - Documentation updated (deployment/unified.md) describing the PUID collision bug, the root cause, and the gosu-based fix. This resolves cases where PUID collides with existing system users (e.g., nobody) which previously caused processes to run with the wrong GID and produce EACCES errors.
24 KiB
Unified Container Deployment
Status: ✅ Implemented
Single container with PostgreSQL, Redis, and Next.js app combined.
Overview
All-in-one Docker image for simple deployment. PostgreSQL + Redis + App in single container with automatic secret generation and minimal configuration.
Key Details
Architecture:
- PostgreSQL 16 (internal, 127.0.0.1:5432)
- Redis 7 (internal, 127.0.0.1:6379)
- Next.js app (exposed, 0.0.0.0:3030)
- Supervisord manages all processes
Image: ghcr.io/kikootwo/readmeabook:latest
Auto-generated secrets (persisted to /app/config/.secrets):
JWT_SECRET- Random 32-byte base64JWT_REFRESH_SECRET- Random 32-byte base64CONFIG_ENCRYPTION_KEY- Random 32-byte base64POSTGRES_PASSWORD- Random 32-byte base64PLEX_CLIENT_IDENTIFIER- Random hex ID- Note: Secrets are generated once on first run and reused on subsequent container restarts
Volumes:
/app/config- App config/logs (bind mount: ./config)/app/cache- Thumbnail cache (bind mount: ./cache)/downloads- Torrent downloads (bind mount: ./downloads)/media- Plex library (bind mount: ./media)/var/lib/postgresql/data- PostgreSQL data (bind mount: ./pgdata)/var/lib/redis- Redis data (bind mount: ./redis)
Dockerfile Structure
Base: node:20-bookworm (debian with node)
Installed packages:
- postgresql-15
- redis-server
- supervisor
- curl, openssl
Build process:
- Install dependencies (production only)
- Generate Prisma client
- Build Next.js app
- Create directories (postgres, redis, app)
- Copy supervisord.conf and entrypoint.sh
Files:
dockerfile.unified- Main Dockerfiledocker/unified/supervisord.conf- Process manager configdocker/unified/entrypoint.sh- Startup script
Startup Sequence
Entrypoint script (entrypoint.sh):
- Load secrets from
/app/config/.secretsif exists - Generate secrets if not provided (from env or file)
- Persist secrets to
/app/config/.secretsfor future restarts - Initialize PostgreSQL if first run
- Start PostgreSQL temporarily
- Create database user and database
- Run Prisma migrations
- Stop PostgreSQL
- Export environment variables
- Start supervisord (postgres → redis → app)
Supervisord priorities:
- PostgreSQL: priority 10 (starts first)
- Redis: priority 20 (starts second)
- App: priority 30 (starts last)
Logs: All services output to stdout/stderr (visible in docker logs)
Deployment
Production (Pre-built Image)
Docker Compose:
services:
readmeabook:
image: ghcr.io/kikootwo/readmeabook:latest
ports:
- "3030:3030"
volumes:
- ./config:/app/config
- ./cache:/app/cache
- ./downloads:/downloads
- ./media:/media
- ./pgdata:/var/lib/postgresql/data
- ./redis:/var/lib/redis
environment:
# RECOMMENDED: Set to your user/group IDs (run 'id' to find yours)
# Hybrid approach: postgres keeps UID 103, everything else uses PUID:PGID
PUID: 1000
PGID: 1000
# Optional overrides:
# JWT_SECRET: "custom"
# PUBLIC_URL: "https://example.com"
Usage:
docker compose -f docker-compose.unified.yml up -d
docker logs -f readmeabook-unified
Local Development (Build Locally)
For faster iteration without waiting for CI/CD:
# Build and run locally (rebuilds on every up)
docker compose -f docker-compose.local.yml up -d --build
# View logs
docker logs -f readmeabook-local
# Rebuild after changes
docker compose -f docker-compose.local.yml up -d --build
# Stop
docker compose -f docker-compose.local.yml down
Build time: ~2-3 minutes (vs 10 minutes on CI/CD)
Note: docker-compose.local.yml uses build: instead of image: to build from dockerfile.unified
Docker Run:
docker run -d \
--name readmeabook \
-p 3030:3030 \
-e PUID=1000 \
-e PGID=1000 \
-v ./config:/app/config \
-v ./cache:/app/cache \
-v ./downloads:/downloads \
-v ./media:/media \
-v ./pgdata:/var/lib/postgresql/data \
-v ./redis:/var/lib/redis \
ghcr.io/kikootwo/readmeabook:latest
Environment Variables
Recommended (for bind mount permissions):
PUID- User ID for file ownership (default: uses system defaults)PGID- Group ID for file ownership (default: uses system defaults)
All optional (auto-generated if not set):
JWT_SECRET- JWT signing keyJWT_REFRESH_SECRET- Refresh token keyCONFIG_ENCRYPTION_KEY- DB encryption keyPOSTGRES_PASSWORD- Postgres passwordPOSTGRES_USER- Postgres user (default: readmeabook)POSTGRES_DB- Database name (default: readmeabook)PLEX_CLIENT_IDENTIFIER- Plex client IDPLEX_PRODUCT_NAME- Plex product nameLOG_LEVEL- Log level (default: info)PUBLIC_URL- Public URL for callbacks
Internal (set automatically):
DATABASE_URL- Built from postgres varsREDIS_URL- redis://127.0.0.1:6379NODE_ENV- productionPORT- 3030HOSTNAME- 0.0.0.0
PUID/PGID Configuration (Hybrid Approach - Recommended)
Problem: Bind-mounted volumes (./pgdata, ./redis, ./config, etc.) may have permission issues, especially with:
- LXC containers with user namespace mapping
- NFS/CIFS mounts
- Running Docker as non-root user
- Multiple users accessing the same files
Solution: Hybrid PUID/PGID mapping that maintains PostgreSQL compatibility while giving you ownership of important files.
How the Hybrid Approach Works
PostgreSQL requires that the database cluster owner have a specific username ("postgres"), which prevents full user remapping. The hybrid approach solves this:
User Remapping Strategy:
postgresuser: Keeps UID 103 (PostgreSQL requirement), remaps GID → PGIDredisuser: Fully remapped to PUID:PGIDnodeuser: Fully remapped to PUID:PGID
File Ownership Result:
- PostgreSQL data (
/var/lib/postgresql/data): 103:PGID - Redis data (
/var/lib/redis): PUID:PGID ✅ Your user owns it - App config (
/app/config): PUID:PGID ✅ Your user owns it - Downloads (
/downloads): PUID:PGID ✅ Your user owns it - Media (
/media): PUID:PGID ✅ Your user owns it
Key Benefits:
- ✅ You fully own downloads, media, and config directories
- ✅ PostgreSQL works correctly (no username conflicts)
- ✅ All files accessible via shared PGID group
- ✅ Minimal LXC mapping needed (only UID 103)
Usage
Standard Docker Setup:
services:
readmeabook:
image: ghcr.io/kikootwo/readmeabook:latest
environment:
PUID: 1000 # Your user ID
PGID: 1000 # Your group ID
volumes:
- ./config:/app/config
- ./pgdata:/var/lib/postgresql/data
- ./redis:/var/lib/redis
- ./downloads:/downloads
- ./media:/media
Find your PUID/PGID:
id
# Output: uid=1000(youruser) gid=1000(yourgroup)
LXC Configuration
For LXC with user namespace mapping, you only need to passthrough container UID 103 (postgres):
Example LXC Config:
# File: /etc/pve/lxc/<CTID>.conf
# Map most UIDs normally (0-102 → 100000-100102)
lxc.idmap: u 0 100000 103
lxc.idmap: g 0 100000 103
# Passthrough postgres UID 103 to host UID 103
lxc.idmap: u 103 103 1
lxc.idmap: g 103 100103 1
# Map remaining UIDs (104-65536 → 100104-165536)
lxc.idmap: u 104 100104 65432
lxc.idmap: g 104 100104 65432
Alternative: Map to your user:
# If you want PostgreSQL files accessible as your host user (UID 1000):
lxc.idmap: u 0 100000 103
lxc.idmap: g 0 100000 103
lxc.idmap: u 103 1000 1 # Map container 103 → host 1000
lxc.idmap: g 103 1000 1
lxc.idmap: u 104 100104 65432
lxc.idmap: g 104 100104 65432
# Then set in docker-compose.yml:
environment:
PUID: 1000
PGID: 1000
Startup Logs
When PUID/PGID are set, you'll see:
🔧 PUID/PGID detected - Configuring hybrid user mapping for 1000:1000
Current UIDs: postgres=103 redis=102 node=1000
Applying hybrid mapping strategy:
- postgres: Keep UID 103, remap GID → 1000 (PostgreSQL compatibility)
- redis: Remap to 1000:1000 (full user ownership)
- node: Remap to 1000:1000 (full user ownership)
✅ User mapping complete!
File ownership will be:
- PostgreSQL data (/var/lib/postgresql/data): 103:1000
- Redis data (/var/lib/redis): 1000:1000
- App config (/app/config): 1000:1000
- Downloads (/downloads): 1000:1000
- Media (/media): 1000:1000
On your host, these will appear as:
- PostgreSQL: UID 103, GID 1000 (readable via group)
- Everything else: Your user (1000:1000)
File Permissions
The container uses group-friendly permissions:
| Directory | Ownership | Permissions | Description |
|---|---|---|---|
/var/lib/postgresql/data |
103:PGID | 750 (rwxr-x---) | PostgreSQL data, group-readable |
/var/lib/redis |
PUID:PGID | 770 (rwxrwx---) | Redis data, group-writable |
/app/config |
PUID:PGID | 775 (rwxrwxr-x) | App config, group-writable |
/app/cache |
PUID:PGID | 775 (rwxrwxr-x) | Thumbnail cache, group-writable |
/downloads |
PUID:PGID | 775 (rwxrwxr-x) | Torrent downloads, group-writable |
/media |
PUID:PGID | 775 (rwxrwxr-x) | Plex library, group-writable |
Your host user (PUID:PGID) can:
- ✅ Fully read/write: downloads, media, config, cache, redis
- ✅ Read (via group): PostgreSQL data
Without PUID/PGID (Default Behavior)
If you don't set PUID/PGID, the container uses default system users:
Default ownership:
- PostgreSQL data: postgres (UID 103)
- Redis data: redis (UID 102)
- App/Downloads: node (UID 1000)
This works fine for most deployments, but files will have different owners on the host.
GitHub Action
File: .github/workflows/build-unified-image.yml
Triggers:
- Push to
mainbranch - Tags matching
v* - Manual workflow dispatch
- Pull requests (build only, no push)
Platforms:
- linux/amd64
- linux/arm64
Tags:
latest(main branch)v1.2.3(version tags)v1.2(minor version)v1(major version)main-<sha>(commit SHA)
Registry: GitHub Container Registry (ghcr.io)
Permissions: Uses GITHUB_TOKEN (no manual setup needed)
Logs
View all logs:
docker logs readmeabook-unified
docker logs -f readmeabook-unified # Follow
Filter by service:
docker logs readmeabook-unified 2>&1 | grep "postgresql"
docker logs readmeabook-unified 2>&1 | grep "redis"
docker logs readmeabook-unified 2>&1 | grep "app"
Supervisord manages log output:
- All stdout → container stdout
- All stderr → container stderr
- No log files (everything to console)
Troubleshooting
Container fails with permission errors:
Symptom: "Operation not permitted" or "Failed to set ownership" in logs
Solution: Set PUID and PGID in docker-compose.yml
# 1. Find your user ID and group ID
id
# Output: uid=1000(youruser) gid=1000(yourgroup)
# 2. Update docker-compose.yml:
services:
readmeabook:
environment:
PUID: 1000 # Use your UID from step 1
PGID: 1000 # Use your GID from step 1
# 3. Restart container
docker compose down
docker compose up -d
LXC Permission Issues:
Symptom: Files owned by UID 103 on host are not accessible
Solution: Configure LXC idmap to passthrough UID 103
# Edit /etc/pve/lxc/<CTID>.conf and add:
lxc.idmap: u 0 100000 103
lxc.idmap: g 0 100000 103
lxc.idmap: u 103 103 1 # Passthrough postgres UID
lxc.idmap: g 103 100103 1
lxc.idmap: u 104 100104 65432
lxc.idmap: g 104 100104 65432
# Then restart LXC container
pct stop <CTID>
pct start <CTID>
WSL2 Permission Errors:
Symptom: "Failed to set ownership" or "Operation not permitted" on /mnt/c/ filesystem
Cause: Windows filesystem doesn't support Linux permissions when using bind mounts
Solution 1: Use Docker volumes (RECOMMENDED for WSL2)
# In docker-compose.yml, replace bind mounts:
volumes:
- ./pgdata:/var/lib/postgresql/data
- ./redis:/var/lib/redis
# With named volumes:
volumes:
- pgdata:/var/lib/postgresql/data
- redis:/var/lib/redis
# Add at bottom of file:
volumes:
pgdata:
redis:
This stores data in Docker-managed volumes which support full permissions.
Solution 2: Move project to Linux filesystem
# Move to Linux filesystem
cd ~
mkdir readmeabook
cd readmeabook
# Copy compose file
cp /mnt/c/git/readmeabook/docker-compose.yml .
# Start container
docker compose up -d
Solution 3: Delete existing directories and let Docker create them
# Stop container and remove directories
docker compose down
rm -rf pgdata redis config cache
# Start fresh - Docker will create directories with correct ownership
docker compose up -d
Database access:
docker exec -it readmeabook-unified \
su - postgres -c "psql -h 127.0.0.1 -U readmeabook"
Redis test:
docker exec readmeabook-unified redis-cli ping
# Should return: PONG
Check migrations:
docker exec readmeabook-unified \
su - node -c "cd /app && npx prisma migrate status"
Reset database:
# Stop container and remove database directory
docker compose -f docker-compose.unified.yml down
rm -rf ./pgdata
docker compose -f docker-compose.unified.yml up -d
Backup/Restore
Backup:
docker exec readmeabook-unified \
su - postgres -c "pg_dump -h 127.0.0.1 -U readmeabook readmeabook" \
> backup.sql
Restore:
cat backup.sql | docker exec -i readmeabook-unified \
su - postgres -c "psql -h 127.0.0.1 -U readmeabook readmeabook"
Migration: Named Volumes → Bind Mounts
Migrating from Docker named volumes to local directories:
- Stop the container:
docker compose -f docker-compose.unified.yml down
- Copy data from named volumes to local directories:
# Create local directories
mkdir -p ./pgdata ./redis ./cache
# Copy PostgreSQL data
docker run --rm -v readmeabook-pgdata:/source -v $(pwd)/pgdata:/dest alpine sh -c "cp -a /source/. /dest/"
# Copy Redis data
docker run --rm -v readmeabook-redis:/source -v $(pwd)/redis:/dest alpine sh -c "cp -a /source/. /dest/"
# Copy cache data
docker run --rm -v readmeabook-cache:/source -v $(pwd)/cache:/dest alpine sh -c "cp -a /source/. /dest/"
-
Update docker-compose.unified.yml (already updated to use bind mounts)
-
Start container with bind mounts:
docker compose -f docker-compose.unified.yml up -d
- Verify data integrity:
docker logs readmeabook-unified
docker exec readmeabook-unified redis-cli ping
- Remove old named volumes (optional):
docker volume rm readmeabook-pgdata readmeabook-redis readmeabook-cache
Benefits of bind mounts:
- ✅ Easy backup/restore (standard filesystem tools)
- ✅ Direct access to data files
- ✅ Simpler migration between hosts
- ✅ No hidden volume location
- ✅ No manual ownership configuration needed (entrypoint handles it)
vs Multi-Container
Unified advantages:
- ✅ Single container (simple)
- ✅ No external dependencies
- ✅ Auto-configured networking
- ✅ Minimal environment variables
Multi-container advantages:
- ✅ Independent service scaling
- ✅ Separate backups
- ✅ External DB access
- ✅ Resource limits per service
Use unified for: Simple deployments, single-host, easy updates Use multi-container for: Complex setups, scaling, orchestration
Security Notes
- Auto-generated secrets: Secure by default (32-byte random), persisted to
/app/config/.secrets - Secrets persistence: Auto-generated secrets are saved to
/app/config/.secretson first run and reused on subsequent starts - Override in production: Set environment variables in docker-compose.yml to use custom secrets (takes precedence over file)
- Protect secrets file: Ensure
/app/configvolume has appropriate permissions (chmod 600 on .secrets file) - No external DB access: PostgreSQL bound to 127.0.0.1
- No external Redis access: Redis bound to 127.0.0.1
- Use reverse proxy: HTTPS termination (Nginx, Caddy, Traefik)
Fixed Issues ✅
1. PostgreSQL initialization
- Issue: First-run database creation
- Fix: Entrypoint script initializes and creates user/database
2. Multi-process logging
- Issue: Need logs from all services
- Fix: Supervisord configured with stdout/stderr to /dev/stdout|stderr
3. Secret management
- Issue: Users need to set many secrets
- Fix: Auto-generate all secrets on first run with openssl
4. Startup ordering
- Issue: App starts before DB ready
- Fix: Supervisord priorities + entrypoint pre-starts DB for init
5. Prisma migrations
- Issue: Need to run migrations before app starts
- Fix: Entrypoint runs
prisma db pushafter DB init
6. Bind mount permissions
- Issue: Container fails with "Operation not permitted" when using bind mounts (./pgdata, ./redis, ./cache)
- Cause: Docker creates bind mount directories with root ownership, postgres/redis/node users cannot write
- Fix: Entrypoint sets correct ownership before initialization (chown postgres:postgres, redis:redis, node:node)
7. Missing server.js in standalone build
- Issue: App fails with "Cannot find module '/app/server.js'"
- Cause: Next.js standalone output creates server.js in
.next/standalone/, needs to be copied to/app/ - Fix: Dockerfile copies standalone output to root:
cp -r .next/standalone/* .
8. DATABASE_URL with special characters
- Issue: Prisma fails with "invalid port number in database URL" when password has special chars
- Cause: Auto-generated passwords can contain characters that need URL encoding (@, #, $, etc.)
- Fix: Entrypoint URL-encodes password before constructing DATABASE_URL
9. Stale Prisma client in Docker builds
- Issue: TypeScript errors about missing Prisma fields during build (e.g.,
plexHomeUserId does not exist) - Cause:
COPY . .copies stalesrc/generated/prismafrom local filesystem, overwriting fresh generation - Fix: Generate Prisma client AFTER copying code + add
src/generatedto.dockerignore
10. Entrypoint script line endings on Windows/WSL2
- Issue: Container fails with "exec /entrypoint.sh: no such file or directory"
- Cause: Windows CRLF line endings in shell scripts are incompatible with Linux
- Fix: Added
.gitattributesrule (*.sh text eol=lf) + Dockerfile converts line endings (sed -i 's/\r$//')
11. PostgreSQL config file mismatch
- Issue: App fails with "password authentication failed" / "Role 'readmeabook' does not exist"
- Cause: supervisord used system config (
/etc/postgresql/15/main/postgresql.conf) which overrides trust auth configured in data directory - Fix: Remove
-c config_file=from supervisord.conf, use data directory's postgresql.conf (standard behavior)
12. Prisma migrations run before PostgreSQL available
- Issue: "P1001: Can't reach database server" during entrypoint migrations
- Cause: Migrations ran after PostgreSQL was stopped, before supervisord started it
- Fix: Run migrations while PostgreSQL is still running in entrypoint, then stop it
13. Scheduled jobs not initialized (setup wizard errors)
- Issue: Setup wizard shows "Job configuration not found" for Audible/Plex jobs
- Cause:
/api/initendpoint never called, soschedulerService.start()never runs and default jobs aren't created - Fix: Created
app-start.shwrapper script that starts server then calls/api/init, supervisord uses wrapper instead of direct node command
14. PostgreSQL binary mismatch in supervisord
- Issue: Container logs
spawnerr: can't find command '/usr/lib/postgresql/15/bin/postgres'and app can't reach DB. - Cause: Base image upgraded to PostgreSQL 16 but supervisord still referenced
/usr/lib/postgresql/15/bin/postgres. - Fix: Update
docker/unified/supervisord.confto call/usr/lib/postgresql/16/bin/postgres.
15. Setup middleware hairpin fetch failures
- Issue: Middleware logs
Setup check failed: Error: fetch failedon every request when the container cannot resolve the public hostname. - Cause: Setup check used the incoming Host header only, so DNS hairpinning or air-gapped domains blocked loopback fetches.
- Fix: Middleware now tries
SETUP_CHECK_BASE_URL(optional), request origin, thenhttp://127.0.0.1:${PORT|3030}; log noise eliminated once any origin succeeds.
16. Local admin authentication fails after container restart
- Issue: After container restart, local admin (manual registration) login fails with "Invalid username or password"
- Cause: CONFIG_ENCRYPTION_KEY was auto-generated on each container start and not persisted. Passwords are encrypted with bcrypt hash then encrypted again with CONFIG_ENCRYPTION_KEY. When the key changes, decryption fails and password validation fails.
- Fix: Entrypoint script now persists all auto-generated secrets (JWT_SECRET, JWT_REFRESH_SECRET, CONFIG_ENCRYPTION_KEY, POSTGRES_PASSWORD) to
/app/config/.secretsfile which is mounted on a volume. On subsequent starts, secrets are loaded from this file instead of regenerating. - Recovery: If already experiencing this issue, either (1) recreate admin account after updating to fixed version, or (2) if you know the old CONFIG_ENCRYPTION_KEY, set it as environment variable in docker-compose.yml
17. Permission errors with bind mounts and LXC user namespace mapping (Hybrid PUID/PGID)
- Issue: Container fails with "Operation not permitted" when using bind mounts (
./pgdata,./redis). LXC user namespace mapping (container UID 103 → host UID 100103) makes file access complex. - Root cause analysis:
- PostgreSQL requires database cluster owner to be username "postgres" (UID 103)
- Cannot remap postgres UID without breaking PostgreSQL initialization
- Users need ownership of downloads, media, config directories
- Solution: Hybrid PUID/PGID approach:
- postgres user: Keep UID 103 (PostgreSQL compatibility), remap GID → PGID
- redis/node users: Fully remap to PUID:PGID
- Result: Downloads/media/config owned by PUID:PGID, PostgreSQL uses 103:PGID with group-readable permissions
- Usage: Set
PUID=1000andPGID=1000in docker-compose.yml - File ownership:
- PostgreSQL data: 103:PGID (readable via group)
- Downloads/media/config: PUID:PGID (full user ownership)
- LXC configuration: Only need to passthrough UID 103 (much simpler than before)
- Backwards compatible: If PUID/PGID not set, uses default system user IDs
18. WSL2 Windows filesystem incompatibility
- Issue: Container fails when using bind mounts on Windows filesystem (
/mnt/c/) with error "Operation not permitted" - Cause: Windows 9p filesystem doesn't support Linux permission operations (chmod/chown) required by PostgreSQL when using bind mounts
- Fix: Only error when chown actually fails (not preemptively), provide helpful solutions
- Solutions:
- Use Docker named volumes (recommended):
pgdata:/var/lib/postgresql/datainstead of./pgdata:/var/lib/postgresql/data - Move project to Linux filesystem:
~/readmeabookinstead of/mnt/c/ - Let Docker create directories on first run (they'll have correct ownership)
- Use Docker named volumes (recommended):
- Note: Works fine on WSL2 when using Docker volumes or letting container create directories
19. PUID collision causes wrong GID (EACCES: permission denied, mkdir)
- Issue: File organization fails with
EACCES: permission denied, mkdir '/media/...'even though PUID/PGID are set correctly - Symptoms:
docker exec -u $PUID:$PGID container mkdir /media/testworks, but the app fails - Root cause: When PUID collides with an existing system user (e.g., PUID=65534 collides with
nobody), theusermodcommand creates two users with the same UID. When supervisord resolvesuser=nodefrom /etc/passwd, it may resolve to the wrong user's GID - Example: User sets PUID=65534 (nobody), PGID=321601206 (AD group). App runs with UID=65534 but GID=65534 (nogroup) instead of GID=321601206
- Diagnosis: Run
docker exec container ps ax -o user,pid,group,gid,comm- if GID column shows wrong value, this is the issue - Fix: Use
gosufor reliable UID:GID switching that bypasses username resolution- Added
gosupackage to Dockerfile - Created
app-start.shandredis-start.shwrapper scripts that usegosu $PUID:$PGIDto switch users - Supervisord now starts these wrappers as root, and gosu switches to the exact UID:GID
- PUID/PGID are passed via /etc/environment to the wrapper scripts
- Added
- Verification: After fix,
ps ax -o user,pid,group,gid,commwill show correct GID for app and redis processes - Note: This primarily affects users with complex setups (NFS mounts, AD/SSSD groups, PUID=65534)
Related
- Multi-container deployment
- README.unified.md (user guide)
- docker-compose.unified.yml (example)