From 60652e96b4f060b3ae328cc80a0ca05381cd15cc Mon Sep 17 00:00:00 2001 From: Username Date: Wed, 21 Jan 2026 12:17:47 +0100 Subject: [PATCH] containerfile: consolidate to single alpine image --- .gitea/workflows/ci.yml | 48 +++--------------- Containerfile | 37 +++++++------- Containerfile.slim | 69 -------------------------- documentation/kubernetes-deployment.md | 2 +- 4 files changed, 27 insertions(+), 129 deletions(-) delete mode 100644 Containerfile.slim diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 3025dea..043bef8 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -290,7 +290,7 @@ jobs: git clone --depth 1 --branch "${GITHUB_REF_NAME}" \ "https://oauth2:${{ github.token }}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git" . - - name: Build standard image + - name: Build image run: | # Use docker or podman, whichever is available if command -v docker >/dev/null 2>&1; then @@ -304,20 +304,8 @@ jobs: echo "Using: $BUILD_CMD" SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) - $BUILD_CMD build -f Containerfile -t flaskpaste:latest -t flaskpaste:sha-${SHORT_SHA} . - echo "Standard image built" - - - name: Build slim image - run: | - if command -v docker >/dev/null 2>&1; then - BUILD_CMD="docker" - else - BUILD_CMD="podman" - fi - - SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) - $BUILD_CMD build -f Containerfile.slim -t flaskpaste:slim -t flaskpaste:slim-sha-${SHORT_SHA} . - echo "Slim image built" + $BUILD_CMD build -t flaskpaste:latest -t flaskpaste:sha-${SHORT_SHA} . + echo "Image built" $BUILD_CMD images | grep flaskpaste - name: Push to Harbor @@ -344,20 +332,13 @@ jobs: $BUILD_CMD login "${HARBOR_REGISTRY}" \ -u "$HARBOR_USER" -p "$HARBOR_PASS" - # Push standard image - for tag in latest sha-${SHORT_SHA}; do + # Push image with multiple tags (latest, slim for backwards compat, sha) + for tag in latest slim sha-${SHORT_SHA}; do $BUILD_CMD tag flaskpaste:latest "${HARBOR_REGISTRY}/library/flaskpaste:${tag}" $BUILD_CMD push "${HARBOR_REGISTRY}/library/flaskpaste:${tag}" echo "Pushed: ${HARBOR_REGISTRY}/library/flaskpaste:${tag}" done - # Push slim image - for tag in slim slim-sha-${SHORT_SHA}; do - $BUILD_CMD tag flaskpaste:slim "${HARBOR_REGISTRY}/library/flaskpaste:${tag}" - $BUILD_CMD push "${HARBOR_REGISTRY}/library/flaskpaste:${tag}" - echo "Pushed: ${HARBOR_REGISTRY}/library/flaskpaste:${tag}" - done - vuln-scan: name: Harbor Vulnerability Scan runs-on: ubuntu-latest @@ -390,18 +371,11 @@ jobs: echo "Waiting for Harbor to index images..." sleep 15 - # Scan standard image - echo "Triggering vulnerability scan for standard image..." + echo "Triggering vulnerability scan..." python harbor-ctl.py --url https://harbor.mymx.me \ -u "$HARBOR_USER" -p "$HARBOR_PASS" \ scan library flaskpaste -d latest --wait --timeout 180 - # Scan slim image - echo "Triggering vulnerability scan for slim image..." - python harbor-ctl.py --url https://harbor.mymx.me \ - -u "$HARBOR_USER" -p "$HARBOR_PASS" \ - scan library flaskpaste -d slim --wait --timeout 180 - - name: Check for critical vulnerabilities env: HARBOR_USER: ${{ secrets.HARBOR_USER }} @@ -439,11 +413,5 @@ jobs: return 0 } - FAILED=0 - check_vulns latest || FAILED=1 - check_vulns slim || FAILED=1 - - if [ "$FAILED" -eq 1 ]; then - exit 1 - fi - echo "Vulnerability scan passed for all images" + check_vulns latest || exit 1 + echo "Vulnerability scan passed" diff --git a/Containerfile b/Containerfile index cbcb9ea..1a5449d 100644 --- a/Containerfile +++ b/Containerfile @@ -1,15 +1,14 @@ -# FlaskPaste Container Image (Multi-Stage Build) +# FlaskPaste Container Image (Alpine) # Build: podman build -t flaskpaste . # Run: podman run -d -p 5000:5000 -v flaskpaste-data:/app/data flaskpaste # Stage 1: Build dependencies -FROM python:3.11-slim AS builder +FROM python:3.11-alpine AS builder WORKDIR /build -# Install build dependencies -RUN apt update && apt install -y --no-install-recommends gcc \ - && apt clean && rm -rf /var/lib/apt/lists/* +# Install build dependencies for compiled Python packages +RUN apk add --no-cache gcc musl-dev libffi-dev # Create virtual environment and upgrade pip RUN python -m venv /opt/venv @@ -23,19 +22,19 @@ RUN pip install --no-cache-dir -r requirements.txt gunicorn \ && rm -rf /opt/venv/lib/python*/site-packages/setuptools/_vendor/jaraco* -# Stage 2: Runtime image -FROM python:3.11-slim +# Stage 2: Alpine runtime (minimal) +FROM python:3.11-alpine LABEL maintainer="FlaskPaste" -LABEL description="Lightweight secure pastebin REST API" +LABEL description="Minimal secure pastebin REST API" -# Cleanup base image, fix base image vulnerabilities, create non-root user -# Note: System packages upgraded for Trivy scan; app runs from venv -RUN apt clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ - && pip install --no-cache-dir --upgrade pip 'setuptools>=80.0' 'jaraco.context>=6.1.0' \ +# Apply security fixes to base image, remove vendored vulnerable packages +RUN pip install --no-cache-dir --upgrade pip 'setuptools>=80.0' 'jaraco.context>=6.1.0' \ && pip cache purge 2>/dev/null || true \ - && rm -rf /root/.cache /usr/local/lib/python*/site-packages/setuptools/_vendor/jaraco* \ - && groupadd -r flaskpaste && useradd -r -g flaskpaste flaskpaste + && rm -rf /root/.cache /usr/local/lib/python*/site-packages/setuptools/_vendor/jaraco* + +# Create non-root user +RUN addgroup -g 65532 -S flaskpaste && adduser -u 65532 -S -G flaskpaste flaskpaste # Copy virtual environment from builder COPY --from=builder /opt/venv /opt/venv @@ -44,16 +43,16 @@ ENV PATH="/opt/venv/bin:$PATH" WORKDIR /app # Copy application files -COPY app/ ./app/ -COPY wsgi.py . -COPY fpaste . +COPY --chown=65532:65532 app/ ./app/ +COPY --chown=65532:65532 wsgi.py . +COPY --chown=65532:65532 fpaste . -# Create data directory with correct ownership +# Create data directory RUN mkdir -p /app/data && chown -R flaskpaste:flaskpaste /app USER flaskpaste -# Environment defaults +# Environment configuration ENV FLASK_ENV=production ENV FLASKPASTE_DB=/app/data/pastes.db ENV PYTHONUNBUFFERED=1 diff --git a/Containerfile.slim b/Containerfile.slim deleted file mode 100644 index 6499bf0..0000000 --- a/Containerfile.slim +++ /dev/null @@ -1,69 +0,0 @@ -# FlaskPaste Slim Container Image (Alpine) -# Build: podman build -f Containerfile.slim -t flaskpaste:slim . -# Run: podman run -d -p 5000:5000 -v flaskpaste-data:/app/data flaskpaste:slim -# -# Alpine: Minimal base image with musl libc, actively maintained security patches -# For debugging with more tools, use the standard Containerfile instead - -# Stage 1: Build dependencies -FROM python:3.11-alpine AS builder - -WORKDIR /build - -# Install build dependencies for compiled Python packages -RUN apk add --no-cache gcc musl-dev libffi-dev - -# Create virtual environment and upgrade pip -RUN python -m venv /opt/venv -ENV PATH="/opt/venv/bin:$PATH" -RUN pip install --no-cache-dir --upgrade pip wheel - -# Install Python dependencies (includes security pins from requirements.txt) -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt gunicorn \ - && pip cache purge 2>/dev/null || true \ - && rm -rf /opt/venv/lib/python*/site-packages/setuptools/_vendor/jaraco* - - -# Stage 2: Alpine runtime (minimal) -FROM python:3.11-alpine - -LABEL maintainer="FlaskPaste" -LABEL description="Minimal secure pastebin REST API (Alpine)" - -# Apply security fixes to base image, remove vendored vulnerable packages -RUN pip install --no-cache-dir --upgrade pip 'setuptools>=80.0' 'jaraco.context>=6.1.0' \ - && pip cache purge 2>/dev/null || true \ - && rm -rf /root/.cache /usr/local/lib/python*/site-packages/setuptools/_vendor/jaraco* - -# Create non-root user -RUN addgroup -g 65532 -S flaskpaste && adduser -u 65532 -S -G flaskpaste flaskpaste - -# Copy virtual environment from builder -COPY --from=builder /opt/venv /opt/venv -ENV PATH="/opt/venv/bin:$PATH" - -WORKDIR /app - -# Copy application files -COPY --chown=65532:65532 app/ ./app/ -COPY --chown=65532:65532 wsgi.py . -COPY --chown=65532:65532 fpaste . - -# Create data directory -RUN mkdir -p /app/data && chown -R flaskpaste:flaskpaste /app - -USER flaskpaste - -# Environment configuration -ENV FLASK_ENV=production -ENV FLASKPASTE_DB=/app/data/pastes.db -ENV PYTHONUNBUFFERED=1 -ENV PYTHONDONTWRITEBYTECODE=1 - -EXPOSE 5000 - -HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ - CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')" || exit 1 - -CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "--access-logfile", "-", "wsgi:application"] diff --git a/documentation/kubernetes-deployment.md b/documentation/kubernetes-deployment.md index 1361c99..384f73f 100644 --- a/documentation/kubernetes-deployment.md +++ b/documentation/kubernetes-deployment.md @@ -24,7 +24,7 @@ ssh k1s "curl -s http://\$(kubectl -n flaskpaste get pod -o jsonpath='{.items[0] |----------|-------| | Host | k1s (192.168.122.241) | | Namespace | flaskpaste | -| Image | harbor.mymx.me/library/flaskpaste:slim | +| Image | harbor.mymx.me/library/flaskpaste:latest | | Pull Policy | Always | ---