# MuseHub — Production Dockerfile # Build context: ~/ecosystem (parent of musehub/ and muse/). # Multi-stage build: builder installs deps into wheels; runtime copies only the wheels. # # Layer invalidation guide (when to rebuild): # requirements.txt changed → docker compose build musehub # muse/ source changed → rebuild (muse is bundled as a wheel) # Python code changed → no rebuild (override.yml bind-mounts musehub/ tests/ etc.) FROM python:3.14-slim AS builder WORKDIR /app RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ cargo \ libssl-dev \ pkg-config \ && rm -rf /var/lib/apt/lists/* # Build the muse package wheel from source (it lives alongside musehub in the ecosystem). COPY muse/ /tmp/muse/ RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels /tmp/muse COPY musehub/requirements.txt . # Build cryptography from source with a generic aarch64 CPU target so the Rust # backend avoids optional instructions (sha512, sm3, etc.) that Docker Desktop's # ARM VM doesn't expose. --no-binary forces a source build; the flag has no effect # on x86_64 where the pre-built wheel works fine. RUN RUSTFLAGS="-C target-cpu=generic" \ pip wheel --no-cache-dir --no-deps --no-binary cryptography \ --wheel-dir /app/wheels cryptography==48.0.0 # Build all remaining dependencies from pre-built wheels where available. # Exclude cryptography — already built from source above. RUN grep -v '^cryptography' requirements.txt \ | pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r /dev/stdin FROM python:3.14-slim AS runtime WORKDIR /app RUN groupadd -r musehub && useradd -r -g musehub musehub RUN apt-get update && apt-get install -y --no-install-recommends \ libpq5 \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/wheels /wheels RUN pip install --no-cache-dir /wheels/* COPY --chown=musehub:musehub musehub/musehub/ ./musehub/ COPY --chown=musehub:musehub musehub/alembic/ ./alembic/ COPY --chown=musehub:musehub musehub/alembic.ini musehub/pyproject.toml ./ COPY --chown=musehub:musehub musehub/docs/ ./docs/ COPY --chown=musehub:musehub musehub/deploy/ ./deploy/ COPY --chown=musehub:musehub musehub/entrypoint.sh ./entrypoint.sh RUN chmod +x ./entrypoint.sh RUN mkdir -p /data && chown -R musehub:musehub /data && chmod 755 /data USER musehub ENV PYTHONPATH=/app ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 EXPOSE 1337 HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \ CMD python3 -c "import urllib.request, ssl, os; url='https://localhost:1337/healthz' if os.path.exists('/tls/localhost.crt') else 'http://localhost:1337/healthz'; ctx=(lambda c: (setattr(c,'check_hostname',False), setattr(c,'verify_mode',ssl.CERT_NONE), c)[-1])(ssl.create_default_context()) if url.startswith('https') else None; r=urllib.request.urlopen(url, context=ctx); exit(0 if r.status==200 else 1)" || exit 1 ENTRYPOINT ["./entrypoint.sh"]