-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
198 lines (168 loc) · 6.53 KB
/
Dockerfile
File metadata and controls
198 lines (168 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# Multi-stage build for ultra-small production image
FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS builder
WORKDIR /build
# Install build dependencies only
# Note: freetype-dev removed - pulls vulnerable libpng, we don't use fonts
RUN apk add --no-cache \
python3 python3-dev py3-pip \
gcc musl-dev libffi-dev openssl-dev \
jpeg-dev zlib-dev
# Create venv and install Python packages
RUN python3 -m venv /venv
COPY requirements.prod.txt ./
RUN /venv/bin/pip install --no-cache-dir --upgrade pip && \
/venv/bin/pip install --no-cache-dir -r requirements.prod.txt
# Drop build-only Python packaging helpers to reduce the virtualenv footprint
RUN python3 - <<'PY'
from __future__ import annotations
import shutil
import sys
from pathlib import Path
site_packages = Path('/venv/lib') / f"python{sys.version_info.major}.{sys.version_info.minor}" / 'site-packages'
for package in ('pip', 'setuptools', 'wheel'):
package_dir = site_packages / package
if package_dir.exists():
shutil.rmtree(package_dir, ignore_errors=True)
module_path = site_packages / f'{package}.py'
if module_path.exists():
module_path.unlink()
for metadata in site_packages.glob(f"{package.replace('-', '_')}*-info"):
shutil.rmtree(metadata, ignore_errors=True)
bin_dir = Path('/venv/bin')
for script in ('pip', 'pip3', 'pip3.12', 'pip3.13'):
target = bin_dir / script
if target.exists():
target.unlink()
PY
# Strip binaries and clean up
RUN find /venv -type f -name "*.so" -exec strip --strip-unneeded {} + && \
find /venv -name "*.pyc" -delete && \
find /venv -name "__pycache__" -exec rm -rf {} + && \
find /venv -name "test" -type d -exec rm -rf {} + && \
find /venv -name "tests" -type d -exec rm -rf {} + && \
find /venv -name "*.egg-info" -type d -exec rm -rf {} +
# Extract the cleaned site-packages tree so the runtime image can extend the
# system interpreter without copying the full virtualenv hierarchy.
RUN PY_MINOR=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') && \
mkdir -p /runtime/site-packages && \
cp -a "/venv/lib/python${PY_MINOR}/site-packages/." /runtime/site-packages/ && \
python3 - <<'PY'
from __future__ import annotations
import sys
from pathlib import Path
runtime = Path('/runtime/site-packages')
source = f"/venv/lib/python{sys.version_info.major}.{sys.version_info.minor}/site-packages"
for pth_file in runtime.glob('*.pth'):
try:
content = pth_file.read_text()
except OSError:
continue
updated = content.replace(source, str(runtime))
if updated != content:
pth_file.write_text(updated)
PY
# Production stage - minimal runtime
FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659
WORKDIR /app
# Install ONLY runtime dependencies
# Added back: tzdata (required for timezone selection support)
# Note: docker-cli removed - we use Docker Python SDK (docker-py)
# Note: freetype removed - pulls vulnerable libpng, we don't use fonts
# Note: su-exec added for permission handling on Unraid/NAS systems
RUN apk update && \
apk add --no-cache \
python3 \
ca-certificates \
jpeg \
zlib \
tzdata \
su-exec \
expat && \
apk upgrade --no-cache && \
rm -rf /var/cache/apk/*
# Copy cleaned venv from builder
COPY --from=builder /runtime/site-packages /opt/runtime/site-packages
# Strip CPython test suite and ensurepip to reduce the base image size further
# Aggressive stripping: Added pydoc_data, unittest, distutils
RUN python3 - <<'PY'
from __future__ import annotations
import shutil
import sysconfig
from pathlib import Path
stdlib = Path(sysconfig.get_path('stdlib'))
for relative in (
'test',
'ensurepip',
'idlelib',
'tkinter',
'turtledemo',
'lib2to3',
'pydoc_data',
'unittest',
'distutils',
'xmlrpc',
'email/test',
'ctypes/test',
'sqlite3/test'
):
target = stdlib / relative
if target.exists():
shutil.rmtree(target, ignore_errors=True)
dynload = stdlib / 'lib-dynload'
for module in ('_tkinter', '_tkinter_impl', 'tkinter', 'readline'): # defensive clean-up
for candidate in dynload.glob(f'{module}*.so'):
candidate.unlink(missing_ok=True)
for root in (stdlib, Path(sysconfig.get_path('platlib'))):
for pycache in root.rglob('__pycache__'):
shutil.rmtree(pycache, ignore_errors=True)
for compiled in root.rglob('*.pyc'):
compiled.unlink(missing_ok=True)
PY
# Ensure stripped stdlib binaries without keeping binutils around
RUN apk add --no-cache --virtual .strip-deps binutils && \
strip --strip-unneeded /usr/bin/python3 && \
strip --strip-unneeded /usr/lib/libpython3.* && \
find /usr/lib/python3.*/lib-dynload -type f -name "*.so" -exec strip --strip-unneeded {} + && \
apk del .strip-deps && \
# Remove busybox wget applet AFTER all apk operations
# (CVE-2025-60876 - HTTP header injection, not needed by DDC)
rm -f /usr/bin/wget
# Create user
RUN addgroup -g 1000 -S ddc && \
adduser -u 1000 -S ddc -G ddc && \
(addgroup -g 281 -S docker 2>/dev/null || addgroup -S docker) && \
adduser ddc docker
# Copy application code
COPY --chown=ddc:ddc run.py .
COPY --chown=ddc:ddc bot.py .
COPY --chown=ddc:ddc app/ app/
COPY --chown=ddc:ddc utils/ utils/
COPY --chown=ddc:ddc cogs/ cogs/
COPY --chown=ddc:ddc locales/ locales/
COPY --chown=ddc:ddc services/ services/
COPY --chown=ddc:ddc encrypted_assets/ encrypted_assets/
# V2.0 Cache-Only: Only copy cached animations
COPY --chown=ddc:ddc cached_animations/ cached_animations/
COPY --chown=ddc:ddc cached_displays/ cached_displays/
COPY --chown=ddc:ddc scripts/entrypoint.sh /app/entrypoint.sh
# Setup permissions
RUN chmod +x /app/entrypoint.sh && \
mkdir -p /app/config /app/logs /app/scripts && \
mkdir -p /app/config/info /app/config/tasks && \
mkdir -p /app/cached_displays && \
chown -R ddc:ddc /app && \
chmod -R 755 /app && \
chmod -R 750 /app/config /app/logs /app/cached_displays && \
find /app -type d -name '__pycache__' -prune -exec rm -rf {} +
# Environment
ENV PYTHONPATH="/app:/opt/runtime/site-packages" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONOPTIMIZE=1 \
TZ="Europe/Berlin"
# Set default timezone
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Note: We start as root to fix volume permissions on Unraid/NAS systems
# The entrypoint.sh will drop privileges to 'ddc' user after fixing permissions
EXPOSE 9374
ENTRYPOINT ["/app/entrypoint.sh"]