Skip to content

Commit f58ba48

Browse files
Merge pull request #39 from GitTimeraider/develop
Adjustments to allow for read-only
2 parents 56d2a40 + 6dec079 commit f58ba48

File tree

6 files changed

+637
-35
lines changed

6 files changed

+637
-35
lines changed

.env.example

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@ [email protected]
2525
DAYS_BEFORE_EXPIRY=7
2626

2727
# User/Group IDs for file permissions (optional)
28+
# Get your IDs with: id -u (for PUID) and id -g (for GUID)
2829
PUID=1000
29-
PGID=1000
30+
GUID=1000
31+
32+
# Performance Settings
33+
CURRENCY_REFRESH_MINUTES=1440
34+
CURRENCY_PROVIDER_PRIORITY=frankfurter,floatrates,erapi_open
35+
PERFORMANCE_LOGGING=true
3036

3137
# Optional: For production deployment
3238
# FLASK_ENV=production

Dockerfile

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,33 @@ RUN apt-get update \
3232
curl \
3333
&& rm -rf /var/lib/apt/lists/*
3434

35+
# Create application user and group at build time for security hardening
36+
# This supports read-only filesystems and user: directives
37+
RUN groupadd -r -g 1000 appgroup \
38+
&& useradd -r -u 1000 -g appgroup -m -s /bin/bash appuser \
39+
&& mkdir -p /app/instance \
40+
&& chown -R appuser:appgroup /app
41+
3542
# Copy installed packages from builder stage
3643
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
3744
COPY --from=builder /usr/local/bin /usr/local/bin
3845

39-
COPY . .
46+
# Copy application files and set ownership
47+
COPY --chown=appuser:appgroup . .
4048

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

52+
# Create writable directories for read-only filesystem compatibility
53+
RUN mkdir -p /tmp/app-runtime /var/tmp/app \
54+
&& chown -R appuser:appgroup /tmp/app-runtime /var/tmp/app \
55+
&& chmod 755 /tmp/app-runtime /var/tmp/app
56+
4457
ENV FLASK_APP=run.py \
45-
PUID=1000 \
46-
PGID=1000
58+
USER=appuser \
59+
GROUP=appgroup \
60+
UID=1000 \
61+
GID=1000
4762

4863
# Health check configuration
4964
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \

PUID_GUID_GUIDE.md

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
# PUID/GUID Configuration Guide
2+
3+
This document explains how to use custom User ID (PUID) and Group ID (GUID) with the Subscription Tracker, including compatibility with security-hardened deployments.
4+
5+
## Overview
6+
7+
The Subscription Tracker supports multiple user/group configuration approaches:
8+
9+
1. **Build-time users** (security-hardened, read-only compatible)
10+
2. **Runtime PUID/GUID** (traditional approach)
11+
3. **Docker user directive** (Kubernetes/security-conscious environments)
12+
4. **Hybrid approach** (best of both worlds)
13+
14+
## Configuration Methods
15+
16+
### Method 1: Traditional PUID/GUID (Full Compatibility)
17+
18+
**Standard Docker Compose:**
19+
```yaml
20+
version: '3.8'
21+
services:
22+
subscription-tracker:
23+
build: .
24+
environment:
25+
- PUID=1001
26+
- GUID=1001
27+
volumes:
28+
- ./data:/app/instance:rw
29+
```
30+
31+
**Docker Command:**
32+
```bash
33+
docker run -d \
34+
-e PUID=1001 \
35+
-e GUID=1001 \
36+
-v ./data:/app/instance:rw \
37+
subscription-tracker
38+
```
39+
40+
**What happens:**
41+
- Container starts as root
42+
- Entrypoint creates/modifies user to match PUID/GUID
43+
- Privileges are dropped to the custom user
44+
- Full file permission control
45+
46+
### Method 2: Docker User Directive (Kubernetes Compatible)
47+
48+
**Security-Hardened Docker Compose:**
49+
```yaml
50+
version: '3.8'
51+
services:
52+
subscription-tracker:
53+
build: .
54+
user: "1001:1001" # PUID:GUID directly
55+
volumes:
56+
- ./data:/app/instance:rw
57+
```
58+
59+
**Docker Command:**
60+
```bash
61+
docker run -d \
62+
--user 1001:1001 \
63+
-v ./data:/app/instance:rw \
64+
subscription-tracker
65+
```
66+
67+
**What happens:**
68+
- Container starts directly as specified user
69+
- No privilege escalation needed
70+
- Compatible with read-only filesystems
71+
- Kubernetes/security-compliant
72+
73+
### Method 3: Hybrid Approach (Recommended)
74+
75+
**Enhanced Docker Compose:**
76+
```yaml
77+
version: '3.8'
78+
services:
79+
subscription-tracker:
80+
build: .
81+
environment:
82+
- PUID=1001
83+
- GUID=1001
84+
# Fallback user directive for security environments
85+
user: "${PUID:-1000}:${GUID:-1000}"
86+
volumes:
87+
- ./data:/app/instance:rw
88+
```
89+
90+
**What happens:**
91+
- Tries PUID/GUID environment variables first
92+
- Falls back to user directive if environment doesn't allow user creation
93+
- Works in both traditional and security-hardened environments
94+
95+
## Read-Only Filesystem Compatibility
96+
97+
### Problem
98+
With read-only filesystems, the container cannot modify `/etc/passwd` or `/etc/group` to create custom users.
99+
100+
### Solutions
101+
102+
**Option A: Pre-set User Directive**
103+
```yaml
104+
services:
105+
subscription-tracker:
106+
user: "1001:1001"
107+
read_only: true
108+
tmpfs:
109+
- /tmp:size=100M,mode=1777
110+
- /var/tmp:size=10M,mode=1777
111+
```
112+
113+
**Option B: Mount Writable User Files**
114+
```yaml
115+
services:
116+
subscription-tracker:
117+
environment:
118+
- PUID=1001
119+
- GUID=1001
120+
read_only: true
121+
tmpfs:
122+
- /tmp:size=100M,mode=1777
123+
- /var/tmp:size=10M,mode=1777
124+
- /etc/passwd:size=1M,mode=0644
125+
- /etc/group:size=1M,mode=0644
126+
```
127+
128+
## File Permissions
129+
130+
### Data Directory Ownership
131+
132+
**Before Starting Container:**
133+
```bash
134+
# Create data directory with correct ownership
135+
mkdir -p ./data
136+
sudo chown -R 1001:1001 ./data
137+
chmod -R 755 ./data
138+
```
139+
140+
**Docker Compose with Init:**
141+
```yaml
142+
services:
143+
subscription-tracker:
144+
environment:
145+
- PUID=1001
146+
- GUID=1001
147+
volumes:
148+
- ./data:/app/instance:rw
149+
# Fix permissions on startup
150+
command: >
151+
sh -c "
152+
chown -R 1001:1001 /app/instance &&
153+
exec python run.py
154+
"
155+
```
156+
157+
## Troubleshooting
158+
159+
### Issue: Permission Denied
160+
161+
**Symptoms:**
162+
```
163+
PermissionError: [Errno 13] Permission denied: '/app/instance/app.db'
164+
```
165+
166+
**Solutions:**
167+
1. Check data directory ownership:
168+
```bash
169+
ls -la ./data
170+
# Should show: drwxr-xr-x 2 1001 1001
171+
```
172+
173+
2. Fix permissions:
174+
```bash
175+
sudo chown -R 1001:1001 ./data
176+
```
177+
178+
3. Verify PUID/GUID in container:
179+
```bash
180+
docker exec -it container_name id
181+
# Should show: uid=1001 gid=1001
182+
```
183+
184+
### Issue: User Creation Failed
185+
186+
**Symptoms:**
187+
```
188+
WARNING: Cannot modify users in read-only filesystem
189+
```
190+
191+
**Solutions:**
192+
1. Use user directive instead of PUID/GUID:
193+
```yaml
194+
user: "1001:1001"
195+
```
196+
197+
2. Mount writable user files:
198+
```yaml
199+
tmpfs:
200+
- /etc/passwd:size=1M,mode=0644
201+
- /etc/group:size=1M,mode=0644
202+
```
203+
204+
### Issue: Security Policy Violations
205+
206+
**Symptoms:**
207+
- Container fails to start in Kubernetes
208+
- Security scanner flags privilege escalation
209+
210+
**Solutions:**
211+
1. Use security-hardened compose:
212+
```bash
213+
docker-compose -f docker-compose.security.yml up
214+
```
215+
216+
2. Set security context in Kubernetes:
217+
```yaml
218+
securityContext:
219+
runAsUser: 1001
220+
runAsGroup: 1001
221+
runAsNonRoot: true
222+
```
223+
224+
## Best Practices
225+
226+
### For Development
227+
- Use traditional PUID/GUID environment variables
228+
- Mount local directories with proper ownership
229+
- Use standard docker-compose.yml
230+
231+
### For Production
232+
- Use user directive or security-hardened compose
233+
- Implement proper volume management
234+
- Consider using named volumes with init containers
235+
236+
### For Kubernetes
237+
- Always use securityContext
238+
- Never run as root (uid 0)
239+
- Use read-only root filesystem when possible
240+
241+
## Migration Guide
242+
243+
### From PUID/GUID to User Directive
244+
245+
**Old Configuration:**
246+
```yaml
247+
environment:
248+
- PUID=1001
249+
- GUID=1001
250+
```
251+
252+
**New Configuration:**
253+
```yaml
254+
user: "1001:1001"
255+
# Remove PUID/GUID environment variables
256+
```
257+
258+
### From Root to Non-Root
259+
260+
**Old Dockerfile:**
261+
```dockerfile
262+
USER root
263+
```
264+
265+
**New Dockerfile:**
266+
```dockerfile
267+
RUN groupadd -g 1000 appgroup && \
268+
useradd -u 1000 -g appgroup -d /app appuser
269+
USER appuser
270+
```
271+
272+
## Environment Variables Reference
273+
274+
| Variable | Default | Description |
275+
|----------|---------|-------------|
276+
| `PUID` | `1000` | User ID for application |
277+
| `GUID` | `1000` | Group ID for application |
278+
| `USER` | `appuser` | Username (build-time) |
279+
| `GROUP` | `appgroup` | Group name (build-time) |
280+
281+
## Compatibility Matrix
282+
283+
| Deployment Method | Read-Only FS | Kubernetes | Security Scanning | Performance |
284+
|-------------------|-------------|------------|------------------|-------------|
285+
| PUID/GUID Env || ⚠️ |||
286+
| User Directive |||||
287+
| Hybrid Approach |||||
288+
| Build-time User |||||
289+
290+
**Legend:**
291+
- ✅ Full support
292+
- ⚠️ Partial support
293+
- ❌ Not supported
294+
295+
## Quick Reference
296+
297+
**Standard Development:**
298+
```bash
299+
PUID=1001 GUID=1001 docker-compose up
300+
```
301+
302+
**Security-Hardened:**
303+
```bash
304+
docker-compose -f docker-compose.security.yml up
305+
```
306+
307+
**Custom User ID:**
308+
```bash
309+
docker run --user 1001:1001 -v ./data:/app/instance subscription-tracker
310+
```
311+
312+
**Kubernetes:**
313+
```yaml
314+
securityContext:
315+
runAsUser: 1001
316+
runAsGroup: 1001
317+
runAsNonRoot: true
318+
```

0 commit comments

Comments
 (0)