Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ [email protected]
DAYS_BEFORE_EXPIRY=7

# User/Group IDs for file permissions (optional)
# Get your IDs with: id -u (for PUID) and id -g (for GUID)
PUID=1000
PGID=1000
GUID=1000

# Performance Settings
CURRENCY_REFRESH_MINUTES=1440
CURRENCY_PROVIDER_PRIORITY=frankfurter,floatrates,erapi_open
PERFORMANCE_LOGGING=true

# Optional: For production deployment
# FLASK_ENV=production
Expand Down
23 changes: 19 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage
FROM python:3.13-slim as builder

Check warning on line 2 in Dockerfile

View workflow job for this annotation

GitHub Actions / build

The 'as' keyword should match the case of the 'from' keyword

FromAsCasing: 'as' and 'FROM' keywords' casing do not match More info: https://docs.docker.com/go/dockerfile/rule/from-as-casing/

WORKDIR /app

Expand Down Expand Up @@ -32,18 +32,33 @@
curl \
&& rm -rf /var/lib/apt/lists/*

# Create application user and group at build time for security hardening
# This supports read-only filesystems and user: directives
RUN groupadd -r -g 1000 appgroup \
&& useradd -r -u 1000 -g appgroup -m -s /bin/bash appuser \
&& mkdir -p /app/instance \
&& chown -R appuser:appgroup /app

# Copy installed packages from builder stage
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

COPY . .
# Copy application files and set ownership
COPY --chown=appuser:appgroup . .

# Entrypoint will create/update user/group according to PUID/PGID
# Make entrypoint executable
RUN chmod +x /app/docker-entrypoint.sh

# Create writable directories for read-only filesystem compatibility
RUN mkdir -p /tmp/app-runtime /var/tmp/app \
&& chown -R appuser:appgroup /tmp/app-runtime /var/tmp/app \
&& chmod 755 /tmp/app-runtime /var/tmp/app

ENV FLASK_APP=run.py \
PUID=1000 \
PGID=1000
USER=appuser \
GROUP=appgroup \
UID=1000 \
GID=1000

# Health check configuration
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
Expand Down
318 changes: 318 additions & 0 deletions PUID_GUID_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
# PUID/GUID Configuration Guide

This document explains how to use custom User ID (PUID) and Group ID (GUID) with the Subscription Tracker, including compatibility with security-hardened deployments.

## Overview

The Subscription Tracker supports multiple user/group configuration approaches:

1. **Build-time users** (security-hardened, read-only compatible)
2. **Runtime PUID/GUID** (traditional approach)
3. **Docker user directive** (Kubernetes/security-conscious environments)
4. **Hybrid approach** (best of both worlds)

## Configuration Methods

### Method 1: Traditional PUID/GUID (Full Compatibility)

**Standard Docker Compose:**
```yaml
version: '3.8'
services:
subscription-tracker:
build: .
environment:
- PUID=1001
- GUID=1001
volumes:
- ./data:/app/instance:rw
```

**Docker Command:**
```bash
docker run -d \
-e PUID=1001 \
-e GUID=1001 \
-v ./data:/app/instance:rw \
subscription-tracker
```

**What happens:**
- Container starts as root
- Entrypoint creates/modifies user to match PUID/GUID
- Privileges are dropped to the custom user
- Full file permission control

### Method 2: Docker User Directive (Kubernetes Compatible)

**Security-Hardened Docker Compose:**
```yaml
version: '3.8'
services:
subscription-tracker:
build: .
user: "1001:1001" # PUID:GUID directly
volumes:
- ./data:/app/instance:rw
```

**Docker Command:**
```bash
docker run -d \
--user 1001:1001 \
-v ./data:/app/instance:rw \
subscription-tracker
```

**What happens:**
- Container starts directly as specified user
- No privilege escalation needed
- Compatible with read-only filesystems
- Kubernetes/security-compliant

### Method 3: Hybrid Approach (Recommended)

**Enhanced Docker Compose:**
```yaml
version: '3.8'
services:
subscription-tracker:
build: .
environment:
- PUID=1001
- GUID=1001
# Fallback user directive for security environments
user: "${PUID:-1000}:${GUID:-1000}"
volumes:
- ./data:/app/instance:rw
```

**What happens:**
- Tries PUID/GUID environment variables first
- Falls back to user directive if environment doesn't allow user creation
- Works in both traditional and security-hardened environments

## Read-Only Filesystem Compatibility

### Problem
With read-only filesystems, the container cannot modify `/etc/passwd` or `/etc/group` to create custom users.

### Solutions

**Option A: Pre-set User Directive**
```yaml
services:
subscription-tracker:
user: "1001:1001"
read_only: true
tmpfs:
- /tmp:size=100M,mode=1777
- /var/tmp:size=10M,mode=1777
```

**Option B: Mount Writable User Files**
```yaml
services:
subscription-tracker:
environment:
- PUID=1001
- GUID=1001
read_only: true
tmpfs:
- /tmp:size=100M,mode=1777
- /var/tmp:size=10M,mode=1777
- /etc/passwd:size=1M,mode=0644
- /etc/group:size=1M,mode=0644
```

## File Permissions

### Data Directory Ownership

**Before Starting Container:**
```bash
# Create data directory with correct ownership
mkdir -p ./data
sudo chown -R 1001:1001 ./data
chmod -R 755 ./data
```

**Docker Compose with Init:**
```yaml
services:
subscription-tracker:
environment:
- PUID=1001
- GUID=1001
volumes:
- ./data:/app/instance:rw
# Fix permissions on startup
command: >
sh -c "
chown -R 1001:1001 /app/instance &&
exec python run.py
"
```

## Troubleshooting

### Issue: Permission Denied

**Symptoms:**
```
PermissionError: [Errno 13] Permission denied: '/app/instance/app.db'
```

**Solutions:**
1. Check data directory ownership:
```bash
ls -la ./data
# Should show: drwxr-xr-x 2 1001 1001
```

2. Fix permissions:
```bash
sudo chown -R 1001:1001 ./data
```

3. Verify PUID/GUID in container:
```bash
docker exec -it container_name id
# Should show: uid=1001 gid=1001
```

### Issue: User Creation Failed

**Symptoms:**
```
WARNING: Cannot modify users in read-only filesystem
```

**Solutions:**
1. Use user directive instead of PUID/GUID:
```yaml
user: "1001:1001"
```

2. Mount writable user files:
```yaml
tmpfs:
- /etc/passwd:size=1M,mode=0644
- /etc/group:size=1M,mode=0644
```

### Issue: Security Policy Violations

**Symptoms:**
- Container fails to start in Kubernetes
- Security scanner flags privilege escalation

**Solutions:**
1. Use security-hardened compose:
```bash
docker-compose -f docker-compose.security.yml up
```

2. Set security context in Kubernetes:
```yaml
securityContext:
runAsUser: 1001
runAsGroup: 1001
runAsNonRoot: true
```

## Best Practices

### For Development
- Use traditional PUID/GUID environment variables
- Mount local directories with proper ownership
- Use standard docker-compose.yml

### For Production
- Use user directive or security-hardened compose
- Implement proper volume management
- Consider using named volumes with init containers

### For Kubernetes
- Always use securityContext
- Never run as root (uid 0)
- Use read-only root filesystem when possible

## Migration Guide

### From PUID/GUID to User Directive

**Old Configuration:**
```yaml
environment:
- PUID=1001
- GUID=1001
```

**New Configuration:**
```yaml
user: "1001:1001"
# Remove PUID/GUID environment variables
```

### From Root to Non-Root

**Old Dockerfile:**
```dockerfile
USER root
```

**New Dockerfile:**
```dockerfile
RUN groupadd -g 1000 appgroup && \
useradd -u 1000 -g appgroup -d /app appuser
USER appuser
```

## Environment Variables Reference

| Variable | Default | Description |
|----------|---------|-------------|
| `PUID` | `1000` | User ID for application |
| `GUID` | `1000` | Group ID for application |
| `USER` | `appuser` | Username (build-time) |
| `GROUP` | `appgroup` | Group name (build-time) |

## Compatibility Matrix

| Deployment Method | Read-Only FS | Kubernetes | Security Scanning | Performance |
|-------------------|-------------|------------|------------------|-------------|
| PUID/GUID Env | ❌ | ⚠️ | ❌ | ✅ |
| User Directive | ✅ | ✅ | ✅ | ✅ |
| Hybrid Approach | ✅ | ✅ | ✅ | ✅ |
| Build-time User | ✅ | ✅ | ✅ | ✅ |

**Legend:**
- ✅ Full support
- ⚠️ Partial support
- ❌ Not supported

## Quick Reference

**Standard Development:**
```bash
PUID=1001 GUID=1001 docker-compose up
```

**Security-Hardened:**
```bash
docker-compose -f docker-compose.security.yml up
```

**Custom User ID:**
```bash
docker run --user 1001:1001 -v ./data:/app/instance subscription-tracker
```

**Kubernetes:**
```yaml
securityContext:
runAsUser: 1001
runAsGroup: 1001
runAsNonRoot: true
```
Loading
Loading