Files
meshcore-bot/Dockerfile
T
agessaman 198536a2fe build(armv7): resolve 32-bit ARM dependencies to wheels via piwheels
Ten dependencies publish no prebuilt armv7 wheel on PyPI (PyNaCl,
pycryptodome, ephem, pyephem, brotli, cffi, MarkupSafe, librt,
backports.zstd, sgmllib3k) and compiled from source on 32-bit ARM. On a
Raspberry Pi 2 that is hours of work and a likely OOM at 1 GB RAM, and in
CI it meant recompiling them under QEMU on every arm/v7 build.

piwheels supplies prebuilt armv7 wheels for those packages. The remaining
two gaps are version skew rather than missing builds: pip prefers the
newest release across indexes, and for brotli and ephem the newest PyPI
release is one step ahead of piwheels' current cp311 build. Holding those
two one version back lets the whole set resolve to wheels.

Measured in a linux/arm/v7 container against requirements.txt:

  PyPI only ............................ 10 compiled from source
  + piwheels extra-index-url ...........  2 compiled from source
  + constraints-armv7.txt ..............  0 compiled - 75/75 wheels

No other package is held back; cryptography, aiohttp, pycryptodome and
PyNaCl all resolve to current releases.

- constraints-armv7.txt: the two pins, with measurements and a command to
  re-check them as piwheels catches up. platform_machine markers make the
  file a no-op if applied off-ARM.
- install-service.sh: configure_armv7_pip_args, gated on uname -m and
  wired into both pip call sites. Warns rather than fails if the
  constraints file is absent.
- Dockerfile: same treatment for the linux/arm/v7 leg only, via
  TARGETPLATFORM. amd64 and arm64 keep resolving from PyPI alone.
- config.ini.pi2-example: document what to do per install method.

Verified: arm/v7 builder stage completes in 2:49 with zero source builds;
amd64 builder stage unaffected with zero piwheels downloads; arch gating
exercised for x86_64, aarch64 and armv7l; 73 config tests pass.

Note that piwheels is now an additional trusted index on the arm/v7 leg
and on 32-bit ARM native installs.
2026-08-06 22:51:37 -07:00

92 lines
3.8 KiB
Docker

# Multi-stage build for meshcore-bot
# Supports: linux/amd64, linux/arm64 (RPi 4/5, 64-bit OS), linux/arm/v7 (RPi 3, 32-bit OS)
# ── builder stage ──────────────────────────────────────────────────────────
FROM python:3.11-slim AS builder
# TARGETPLATFORM is injected by BuildKit for each platform in the matrix.
# Useful for platform-specific build steps if needed in future.
ARG TARGETPLATFORM
ARG TARGETARCH
# Install build dependencies.
# apt cache mounts are scoped per-architecture to avoid cross-contamination.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=apt-lib-$TARGETARCH \
apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libffi-dev \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
COPY requirements.txt pyproject.toml constraints-armv7.txt ./
# Pip cache is scoped per-architecture.
#
# The arm/v7 leg additionally pulls from piwheels. Ten dependencies publish no armv7 wheel
# on PyPI (PyNaCl, pycryptodome, ephem, brotli, cffi, MarkupSafe, librt, backports.zstd,
# sgmllib3k) and would otherwise compile from source under QEMU on every build. piwheels
# supplies prebuilt armv7 wheels; constraints-armv7.txt closes the last two version-skew
# gaps so the leg resolves entirely to wheels. See constraints-armv7.txt for measurements.
#
# Scoped to linux/arm/v7 deliberately: amd64 and arm64 keep resolving from PyPI alone, so
# neither the extra index nor the two held-back versions touch those images.
RUN --mount=type=cache,target=/root/.cache/pip,id=pip-$TARGETARCH \
if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
pip install --user \
--extra-index-url https://www.piwheels.org/simple \
-c constraints-armv7.txt \
-r requirements.txt; \
else \
pip install --user -r requirements.txt; \
fi
# ── runtime stage ──────────────────────────────────────────────────────────
FROM python:3.11-slim
ARG TARGETARCH
# Runtime system packages.
# libbluetooth3 is available on amd64, arm64, and armhf (arm/v7).
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=apt-lib-$TARGETARCH \
apt-get update && apt-get install -y --no-install-recommends \
udev \
libbluetooth3 \
libffi8 \
&& rm -rf /var/lib/apt/lists/*
# Non-root user with dialout group for serial port access.
RUN useradd -m -u 1000 -G dialout,tty meshcore && \
mkdir -p /app /data/config /data/databases /data/logs /data/backups && \
chown -R meshcore:meshcore /app /data
COPY --from=builder --chown=meshcore:meshcore /root/.local /home/meshcore/.local
WORKDIR /app
# Version label for web viewer footer (passed via --build-arg in CI).
ARG MESHCORE_BOT_VERSION
ENV MESHCORE_BOT_VERSION=${MESHCORE_BOT_VERSION}
COPY --chown=meshcore:meshcore . /app/
ENV PATH=/home/meshcore/.local/bin:$PATH \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# OCI image labels for supply-chain transparency.
LABEL org.opencontainers.image.title="meshcore-bot" \
org.opencontainers.image.description="MeshCore Bot for mesh radio networks" \
org.opencontainers.image.source="https://github.com/agessaman/meshcore-bot"
USER meshcore
# Health check: verify PID 1 (the bot process) is still alive.
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD ["sh", "-c", "kill -0 1"]
CMD ["python3", "meshcore_bot.py", "--config", "/data/config/config.ini"]