diff --git a/Dockerfile b/Dockerfile index b5d39a5..2bcdfea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,12 @@ +# Docker Build Stages: +# 1. build-frontend: Build static frontend assets using Node +# 2. builder: Install Python dependencies, build and collect backend files in a venv +# 3. final image: Copy venv, install runtime deps, set up container user and config + +# ---- STAGE 1: Frontend Build ---- ARG NODE_IMAGE=node:24-alpine ARG NODE_HASH=sha256:0340fa682d72068edf603c305bfbc10e23219fb0e40df58d9ea4d6f33a9798bf -ARG PYTHON_IMAGE=python:3.12.12-alpine3.23 -ARG PYTHON_HASH=sha256:036871e8860c254533e1d4c2842568f19a56d1afbaed99653ee6206bf9491f6e -# Stage 1: Build Frontend FROM ${NODE_IMAGE}@${NODE_HASH} AS build-frontend WORKDIR /src COPY package.json pnpm-lock.yaml vite.config.js tailwind.config.js postcss.config.js ./ @@ -12,36 +15,35 @@ RUN corepack enable && corepack prepare pnpm@10.30.0 --activate && \ pnpm install --frozen-lockfile && \ pnpm run build-frontend -# Stage 2: Build Backend & Virtual Environment +# ---- STAGE 2: Python Builder ---- +ARG PYTHON_IMAGE=python:3.12.12-alpine3.23 +ARG PYTHON_HASH=sha256:036871e8860c254533e1d4c2842568f19a56d1afbaed99653ee6206bf9491f6e + FROM ${PYTHON_IMAGE}@${PYTHON_HASH} AS builder WORKDIR /build -# Install build dependencies for C-extensions RUN apk upgrade --no-cache && \ apk add --no-cache gcc musl-dev linux-headers python3-dev libffi-dev openssl-dev git -# Setup venv and install dependencies + RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" + COPY pyproject.toml poetry.lock ./ RUN pip install --no-cache-dir --upgrade "pip>=26.0" poetry setuptools wheel "jaraco.context>=6.1.0" && \ poetry config virtualenvs.create false && \ poetry install --no-root --only main && \ rm -rf /root/.cache/pip /root/.cache/pypoetry -# Copy source code and built frontend COPY meshchatx ./meshchatx COPY --from=build-frontend /src/meshchatx/public ./meshchatx/public -# Install the package itself into the venv RUN pip install --no-cache-dir . && \ - # Trigger LXST filter compilation while build tools are still present python -c "import LXST.Filters; print('LXST Filters compiled successfully')" && \ python -m compileall /opt/venv/lib/python3.12/site-packages -# Stage 3: Final Runtime Image +# ---- STAGE 3: Final Image ---- FROM ${PYTHON_IMAGE}@${PYTHON_HASH} WORKDIR /app -# Install runtime dependencies only -# We keep py3-setuptools because CFFI/LXST might need it at runtime on Python 3.12+ + RUN apk upgrade --no-cache && \ apk add --no-cache ffmpeg opusfile libffi py3-setuptools espeak-ng && \ python -m pip install --no-cache-dir --upgrade "pip>=26.0" "jaraco.context>=6.1.0" && \ @@ -49,14 +51,11 @@ RUN apk upgrade --no-cache && \ addgroup -g 1000 meshchat && adduser -u 1000 -G meshchat -S meshchat && \ mkdir -p /config && chown meshchat:meshchat /config -# Copy the virtual environment from the build stage COPY --from=builder --chown=meshchat:meshchat /opt/venv /opt/venv -# Set up environment ENV PATH="/opt/venv/bin:$PATH" ENV PYTHONUNBUFFERED=1 ENV PYTHONDONTWRITEBYTECODE=1 USER meshchat -# Run the app using the installed 'meshchat' entrypoint CMD ["meshchat", "--host=0.0.0.0", "--reticulum-config-dir=/config/.reticulum", "--storage-dir=/config/.meshchat", "--headless"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index b7e46ee..040ec1d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -6,33 +6,9 @@ services: container_name: reticulum-meshchatx image: reticulum-meshchatx:local restart: unless-stopped - # Permission handling is now automated in the Dockerfile via su-exec - # reticulum-meshchatx will run as user 'meshchat' (UID 1000) security_opt: - no-new-privileges:true - # Make the meshchat web interface accessible from the host on port 8000 ports: - 127.0.0.1:8000:8000 volumes: - ./meshchat-config:/config - # Uncomment if you have a USB device connected, such as an RNode - # devices: - # - /dev/ttyUSB0:/dev/ttyUSB0 - # - # Host network for autointerface: - # network_mode: host - -# LibreTranslate - optional - -# libretranslate: -# container_name: libretranslate -# image: libretranslate/libretranslate:latest -# ports: -# - 127.0.0.1:5000:5000 -# restart: unless-stopped -# healthcheck: -# test: ["CMD", "curl", "-f", "http://localhost:5000/health"] -# interval: 10s -# timeout: 4s -# retries: 4 -# start_period: 5s diff --git a/docker-compose.yml b/docker-compose.yml index 0d9026a..7e0a144 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,29 +5,7 @@ services: restart: unless-stopped security_opt: - no-new-privileges:true - # Make the meshchat web interface accessible from the host on port 8000 ports: - 127.0.0.1:8000:8000 volumes: - ./meshchat-config:/config - # Uncomment if you have a USB device connected, such as an RNode - # devices: - # - /dev/ttyUSB0:/dev/ttyUSB0 - # - # Host network for autointerface: - # network_mode: host - -# LibreTranslate - optional - -# libretranslate: -# container_name: libretranslate -# image: libretranslate/libretranslate:latest -# ports: -# - 127.0.0.1:5000:5000 -# restart: unless-stopped -# healthcheck: -# test: ["CMD", "curl", "-f", "http://localhost:5000/health"] -# interval: 10s -# timeout: 4s -# retries: 4 -# start_period: 5s