148 lines
6.7 KiB
Docker
148 lines
6.7 KiB
Docker
# Multi-stage Dockerfile for WebSSH - Optimized for small image size
|
|
# Stage 1: Builder - compiles Python packages with build dependencies
|
|
FROM python:3.11-slim AS builder
|
|
|
|
# Install build dependencies needed for compiling cryptography/pynacl on arm/v7
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
gcc \
|
|
make \
|
|
python3-dev \
|
|
libffi-dev \
|
|
libssl-dev \
|
|
libsodium-dev \
|
|
curl \
|
|
ca-certificates \
|
|
binutils \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Install Python dependencies into a virtual environment
|
|
RUN python -m venv /opt/venv
|
|
ENV PATH="/opt/venv/bin:$PATH"
|
|
|
|
COPY requirements.txt /tmp/
|
|
COPY src/ /tmp/src/
|
|
# Prefer prebuilt wheels when available to avoid building larger compiled artifacts
|
|
RUN pip install --no-cache-dir --prefer-binary -r /tmp/requirements.txt
|
|
|
|
# Strip compiled extensions and remove tests/pycache in venv to reduce size
|
|
RUN set -eux; \
|
|
# Strip native extensions to reduce size when possible
|
|
if command -v strip >/dev/null 2>&1; then \
|
|
find /opt/venv -type f -name '*.so' -print0 | xargs -0 -r strip --strip-unneeded || true; \
|
|
find /opt/venv -type f -executable -print0 | xargs -0 -r file | awk -F: '/ELF/{print $1}' | xargs -0 -r strip --strip-unneeded || true; \
|
|
fi; \
|
|
# Remove Python caches and compiled bytecode
|
|
find /opt/venv -type d -name '__pycache__' -print0 | xargs -0 -r rm -rf || true; \
|
|
find /opt/venv -type f -name '*.pyc' -print0 | xargs -0 -r rm -f || true; \
|
|
# Remove test folders from packages
|
|
find /opt/venv -type d \( -name 'tests' -o -name 'test' \) -print0 | xargs -0 -r rm -rf || true; \
|
|
# Remove static docs and unnecessary package metadata that are not needed at runtime
|
|
find /opt/venv -type d -name 'doc' -print0 | xargs -0 -r rm -rf || true; \
|
|
find /opt/venv -type d -name 'docs' -print0 | xargs -0 -r rm -rf || true; \
|
|
# Remove pip/setuptools/wheel from the venv to save space (not needed at runtime)
|
|
rm -rf /opt/venv/bin/pip* || true; \
|
|
find /opt/venv -path '*/site-packages/pip*' -print0 | xargs -0 -r rm -rf || true; \
|
|
find /opt/venv -path '*/site-packages/setuptools*' -print0 | xargs -0 -r rm -rf || true; \
|
|
find /opt/venv -path '*/site-packages/wheel*' -print0 | xargs -0 -r rm -rf || true; \
|
|
# Remove static metadata not required for runtime (careful: keep runtime metadata intact)
|
|
find /opt/venv -type d -name '*.dist-info' -prune -print0 | xargs -0 -r rm -rf || true || true
|
|
|
|
# Download Caddy binary for the build target (multi-arch aware)
|
|
ARG TARGETARCH
|
|
ARG TARGETVARIANT
|
|
RUN set -eux; \
|
|
DARCH="${TARGETARCH:-$(dpkg --print-architecture)}"; \
|
|
VARIANT="${TARGETVARIANT:-}"; \
|
|
case "$DARCH" in \
|
|
amd64) ARCH=amd64 ;; \
|
|
arm64) ARCH=arm64 ;; \
|
|
arm) if [ "${VARIANT}" = "v7" ]; then ARCH=armv7; else ARCH=arm; fi ;; \
|
|
*) ARCH=amd64 ;; \
|
|
esac; \
|
|
echo "Downloading Caddy for arch=${ARCH}"; \
|
|
# Try multiple candidate arch names for robustness (especially for arm variants)
|
|
candidates="${ARCH}"; \
|
|
if [ "${ARCH}" = "armv7" ]; then candidates="armv7 arm armv6"; fi; \
|
|
if [ "${ARCH}" = "arm" ]; then candidates="arm armv7 armv6"; fi; \
|
|
downloaded=0; \
|
|
for a in $candidates; do \
|
|
echo "Trying caddy download arch=$a"; \
|
|
if curl -fsSL "https://caddyserver.com/api/download?os=linux&arch=$a" -o /tmp/caddy_dl; then \
|
|
echo "Downloaded caddy for arch=$a"; downloaded=1; break; \
|
|
else \
|
|
echo "No binary for arch=$a"; fi; \
|
|
done; \
|
|
if [ "$downloaded" -ne 1 ]; then echo "Failed to download Caddy for any candidate: $candidates" >&2; exit 1; fi; \
|
|
# If it's a tar.gz archive, extract it; otherwise treat as raw binary
|
|
if tar -tzf /tmp/caddy_dl >/dev/null 2>&1; then \
|
|
tar -xzf /tmp/caddy_dl -C /tmp; \
|
|
else \
|
|
mv /tmp/caddy_dl /tmp/caddy_raw || true; \
|
|
fi; \
|
|
mkdir -p /tmp/caddy_bin_dir; \
|
|
# Preferred locations
|
|
if [ -f /tmp/caddy ]; then mv /tmp/caddy /tmp/caddy_bin_dir/caddy; fi; \
|
|
if [ -f /tmp/caddy_linux_${ARCH} ]; then mv /tmp/caddy_linux_${ARCH} /tmp/caddy_bin_dir/caddy; fi; \
|
|
if [ -f /tmp/caddy_raw ]; then mv /tmp/caddy_raw /tmp/caddy_bin_dir/caddy; fi; \
|
|
# Fallback: find any executable named 'caddy' under /tmp
|
|
if [ ! -f /tmp/caddy_bin_dir/caddy ]; then \
|
|
found=$(find /tmp -type f -name caddy -perm /111 -print -quit || true); \
|
|
if [ -n "$found" ]; then mv "$found" /tmp/caddy_bin_dir/caddy; fi; \
|
|
fi; \
|
|
if [ ! -f /tmp/caddy_bin_dir/caddy ]; then echo "Caddy binary not found after download" >&2; exit 1; fi; \
|
|
chmod +x /tmp/caddy_bin_dir/caddy; \
|
|
# Strip Caddy to reduce size
|
|
if command -v strip >/dev/null 2>&1; then strip --strip-unneeded /tmp/caddy_bin_dir/caddy || true; fi
|
|
|
|
# Stage 2: Final - minimal runtime image
|
|
FROM python:3.11-slim
|
|
|
|
# Install only runtime dependencies (no build tools)
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
gosu \
|
|
libsodium23 \
|
|
libffi8 \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Copy Python packages from builder
|
|
COPY --from=builder /opt/venv /opt/venv
|
|
ENV PATH="/opt/venv/bin:$PATH"
|
|
|
|
# Reinstall runtime dependencies explicitly in the final image to avoid missing package metadata
|
|
COPY requirements.txt /tmp/requirements.txt
|
|
RUN /opt/venv/bin/python -m ensurepip --upgrade && /opt/venv/bin/python -m pip install --no-cache-dir --prefer-binary -r /tmp/requirements.txt && rm -f /tmp/requirements.txt
|
|
|
|
# Security: Run as non-root user
|
|
RUN groupadd -r strixwebssh && useradd -r -g strixwebssh strixwebssh
|
|
|
|
WORKDIR /app/src
|
|
|
|
# Copy application and static files
|
|
COPY src/ /app/src/
|
|
COPY deployment/docker-entrypoint.sh /usr/local/bin/
|
|
|
|
# Create .ssh directory for host keys
|
|
RUN mkdir -p /app/src/.ssh && chown -R strixwebssh:strixwebssh /app/src
|
|
|
|
# Copy entrypoint
|
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
|
|
|
# Copy the Caddy binary prepared in the builder stage
|
|
COPY --from=builder /tmp/caddy_bin_dir/caddy /usr/local/bin/caddy
|
|
RUN chmod +x /usr/local/bin/caddy || true
|
|
|
|
# Copy Caddyfile and static assets after package installation to avoid dpkg conffile prompts
|
|
COPY deployment/Caddyfile /etc/caddy/Caddyfile
|
|
COPY src/index.html /srv/
|
|
COPY src/help.html /srv/
|
|
COPY src/static/ /srv/static/
|
|
|
|
# Expose host-facing port for Caddy and internal service ports
|
|
EXPOSE 8080 8081 8082 8083
|
|
|
|
# Health check via Caddy (serving root) using Python (no curl required)
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
CMD python -c "import sys,urllib.request as u;\ntry:\n r=u.urlopen('http://127.0.0.1:8080/', timeout=5);\n code=getattr(r,'status',None) or r.getcode();\n sys.exit(0 if code==200 else 1)\nexcept Exception:\n sys.exit(1)"
|
|
|
|
# Use shell form to see entrypoint errors
|
|
ENTRYPOINT /usr/local/bin/docker-entrypoint.sh |