# syntax=docker/dockerfile:1.6 # Copyright © 2025-26 l5yth & contributors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # NOTE: This Dockerfile is kept for backward compatibility. The canonical build # instructions live in `web/Dockerfile`; keep the two files in sync. # Main application builder stage FROM ruby:3.4-alpine AS builder # Ensure native extensions are built against musl libc rather than # using glibc precompiled binaries (which fail on Alpine). ENV BUNDLE_FORCE_RUBY_PLATFORM=true # Install build dependencies and SQLite3 RUN apk add --no-cache \ build-base \ python3 \ py3-pip \ py3-virtualenv \ sqlite-dev \ linux-headers \ pkgconfig # Set working directory WORKDIR /app # Copy Gemfile and install dependencies COPY web/Gemfile web/Gemfile.lock* ./ # Install gems with SQLite3 support RUN bundle config set --local force_ruby_platform true && \ bundle config set --local without 'development test' && \ bundle install --jobs=4 --retry=3 # Install Meshtastic decoder dependencies in a dedicated venv RUN python3 -m venv /opt/meshtastic-venv && \ /opt/meshtastic-venv/bin/pip install --no-cache-dir meshtastic protobuf # Production stage FROM ruby:3.4-alpine AS production # Build-time toggle controlling whether Chromium is bundled into the image # for runtime Open Graph preview rendering. Operators on size-constrained # hosts can build with `--build-arg WITH_OG_IMAGE=0` to skip Chromium and # its font/library payload (~150 MB). The web app falls back to the # packaged default PNG when Chromium is missing, and operators can point # `OG_IMAGE_URL` at a CDN-hosted preview instead. ARG WITH_OG_IMAGE=1 ENV WITH_OG_IMAGE=${WITH_OG_IMAGE} # Install runtime dependencies. Chromium powers the runtime Open Graph # preview generator; the accompanying font and library packages are the # minimum set required to render the dashboard headlessly on Alpine. RUN apk add --no-cache \ python3 \ sqlite \ tzdata \ curl \ && if [ "$WITH_OG_IMAGE" = "1" ]; then \ apk add --no-cache chromium nss freetype harfbuzz ttf-freefont; \ fi # Create non-root user RUN addgroup -g 1000 -S potatomesh && \ adduser -u 1000 -S potatomesh -G potatomesh # Set working directory WORKDIR /app # Copy installed gems from builder stage COPY --from=builder /usr/local/bundle /usr/local/bundle COPY --from=builder /opt/meshtastic-venv /opt/meshtastic-venv # Copy application code (excluding the Dockerfile which is not required at runtime) COPY --chown=potatomesh:potatomesh web/app.rb ./ COPY --chown=potatomesh:potatomesh web/app.sh ./ COPY --chown=potatomesh:potatomesh web/Gemfile ./ COPY --chown=potatomesh:potatomesh web/Gemfile.lock* ./ COPY --chown=potatomesh:potatomesh web/lib ./lib COPY --chown=potatomesh:potatomesh web/spec ./spec COPY --chown=potatomesh:potatomesh web/public ./public COPY --chown=potatomesh:potatomesh web/views ./views COPY --chown=potatomesh:potatomesh web/scripts ./scripts COPY --chown=potatomesh:potatomesh web/pages ./pages # Copy SQL schema files from data directory COPY --chown=potatomesh:potatomesh data/*.sql /data/ COPY --chown=potatomesh:potatomesh data/mesh_ingestor/decode_payload.py /app/data/mesh_ingestor/decode_payload.py # Create data and configuration directories with correct ownership RUN mkdir -p /app/.local/share/potato-mesh \ && mkdir -p /app/.config/potato-mesh/well-known \ && mkdir -p /app/pages \ && chown -R potatomesh:potatomesh /app/.local/share /app/.config /app/pages # Switch to non-root user USER potatomesh # Expose port EXPOSE 41447 # Baked application version (SPEC AV1). The image ships without `.git` # (`.dockerignore` excludes it), so `determine_app_version`'s in-image `git # describe` would fall back to the constant `Config.version_fallback` and the # `?v=` asset cache-buster would be identical across every build between version # bumps. CI (`.github/workflows/docker.yml`) computes `git describe` from its # full checkout and passes it as `--build-arg APP_VERSION=…`; the app prefers # this ENV, making the buster unique per build so `AssetCacheControl` can serve # versioned JS/CSS `immutable`. Declared late so changing it only rebuilds the # final cheap layers. Empty by default: a plain `docker build` with no build-arg # keeps today's git-lookup / fallback behavior unchanged. ARG APP_VERSION="" ENV APP_VERSION=${APP_VERSION} # Default environment variables (can be overridden by host) ENV RACK_ENV=production \ APP_ENV=production \ MESHTASTIC_PYTHON=/opt/meshtastic-venv/bin/python \ XDG_DATA_HOME=/app/.local/share \ XDG_CONFIG_HOME=/app/.config \ SITE_NAME="PotatoMesh Demo" \ CHANNEL="#LongFast" \ FREQUENCY="915MHz" \ MAP_CENTER="38.761944,-27.090833" \ MAP_ZOOM="" \ MAX_DISTANCE=42 \ CONTACT_LINK="#potatomesh:dod.ngo" \ FERRUM_BROWSER_PATH=/usr/bin/chromium \ MESHTASTIC_PRESET="" \ MESHTASTIC_FREQ="" \ MESHCORE_PRESET="" \ MESHCORE_FREQ="" \ RETICULUM_PRESET="" \ RETICULUM_FREQ="" \ ANNOUNCEMENT="" \ EVENTS=1 \ OG_IMAGE_URL="" \ OG_IMAGE_NO_SANDBOX=1 \ PAGES_DIR=/app/pages \ PROM_REPORT_IDS="" \ FEDERATION=1 \ PRIVATE=0 \ CONNECTION=/dev/ttyACM0 \ INSTANCE_DOMAIN="" \ API_TOKEN="" \ DEBUG=0 # Start the application CMD ["ruby", "app.rb", "-p", "41447", "-o", "0.0.0.0"]