From 61424163da364804e4d1da67740af3aa8cb18262 Mon Sep 17 00:00:00 2001 From: Tobias Huttinger Date: Thu, 30 Apr 2026 21:08:07 +0200 Subject: [PATCH] Added docker deployment --- .dockerignore | 26 ++++++++++++++ Dockerfile | 39 +++++++++++++++++++++ README.md | 27 +++++++++++++++ docker-compose.yml | 25 ++++++++++++++ scripts/build-and-push.sh | 72 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100755 scripts/build-and-push.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8ff7082 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +.git +.gitignore +.github +.venv +venv +env +__pycache__ +**/__pycache__ +*.py[cod] +*.egg-info +.pytest_cache +.ruff_cache +.mypy_cache +build +dist +data +*.db +*.db-journal +*.db-wal +*.db-shm +config.toml +.env +.vscode +.idea +*.swp +README.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..80380f5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# syntax=docker/dockerfile:1.7 + +FROM python:3.12-slim AS builder + +ENV PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PYTHONDONTWRITEBYTECODE=1 + +WORKDIR /build + +COPY pyproject.toml ./ +COPY src ./src + +RUN python -m pip install --upgrade pip build \ + && python -m build --wheel --outdir /wheels + + +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + MESHBOT_CONFIG=/etc/meshbot/config.toml \ + MESHBOT_STORAGE__SQLITE_PATH=/data/meshbot.db + +RUN useradd --system --home /app --shell /usr/sbin/nologin meshbot \ + && mkdir -p /data /etc/meshbot \ + && chown meshbot:meshbot /data + +WORKDIR /app + +COPY --from=builder /wheels/*.whl /tmp/wheels/ +RUN pip install --no-cache-dir /tmp/wheels/*.whl \ + && rm -rf /tmp/wheels + +USER meshbot + +VOLUME ["/data", "/etc/meshbot"] + +ENTRYPOINT ["meshbot"] diff --git a/README.md b/README.md index b5fc9c4..43574b5 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,30 @@ Any field can be overridden via env vars, e.g. `MESHBOT_LLM__API_KEY=sk-...`. - `src/meshbot/llm.py` — `AsyncOpenAI` wrapper. - `src/meshbot/messages.py` — UTF-8-safe byte-length trimming. - `src/meshbot/config.py` — TOML + env-var settings (pydantic-settings). + +## Docker + +Build and push a multi-arch image (`linux/amd64` + `linux/arm64`): + +```sh +docker login registry.example.com # once +export MESHBOT_IMAGE=registry.example.com/team/meshbot +./scripts/build-and-push.sh # tags: latest + +EXTRA_TAGS="v0.1.0" ./scripts/build-and-push.sh # add explicit version +PUSH=0 PLATFORMS=linux/amd64 ./scripts/build-and-push.sh # local load only +``` + +Run via compose (set `MESHBOT_IMAGE`, `MESHBOT_LLM_BASE_URL`, `MESHBOT_LLM_MODEL`, +optionally `MESHBOT_DEVICE`): + +```sh +export MESHBOT_IMAGE=registry.example.com/team/meshbot:latest +export MESHBOT_LLM_BASE_URL=http://llama:8080/v1 +export MESHBOT_LLM_MODEL=llama-3.1-8b-instruct +export MESHBOT_DEVICE=/dev/ttyUSB0 +docker compose up -d +``` + +The container expects `config.toml` mounted at `/etc/meshbot/config.toml` and +persists SQLite to a named volume at `/data`. Any field can still be overridden +via `MESHBOT_
__` env vars. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9df0eff --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + meshbot: + image: ${MESHBOT_IMAGE:?set MESHBOT_IMAGE to your image reference} + container_name: meshbot + restart: unless-stopped + # MeshCore companion is on a USB serial port. Map the host device through to + # the container. Override MESHBOT_DEVICE for ttyACM0 etc. + devices: + - "${MESHBOT_DEVICE:-/dev/ttyUSB0}:${MESHBOT_DEVICE:-/dev/ttyUSB0}" + # Some serial chipsets need access to the dialout group on the host. + group_add: + - dialout + environment: + MESHBOT_MESHCORE__SERIAL_PORT: ${MESHBOT_DEVICE:-/dev/ttyUSB0} + MESHBOT_LLM__BASE_URL: ${MESHBOT_LLM_BASE_URL:?set MESHBOT_LLM_BASE_URL} + MESHBOT_LLM__API_KEY: ${MESHBOT_LLM_API_KEY:-not-needed} + MESHBOT_LLM__MODEL: ${MESHBOT_LLM_MODEL:?set MESHBOT_LLM_MODEL} + volumes: + - meshbot-data:/data + # Mount your config.toml at /etc/meshbot/config.toml. Anything not set in the + # TOML will fall back to defaults; env vars above always win. + - ./config.toml:/etc/meshbot/config.toml:ro + +volumes: + meshbot-data: diff --git a/scripts/build-and-push.sh b/scripts/build-and-push.sh new file mode 100755 index 0000000..1505bed --- /dev/null +++ b/scripts/build-and-push.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Build a multi-arch (linux/amd64, linux/arm64) image and push it to a registry. +# +# Required: +# MESHBOT_IMAGE Full image reference, e.g. registry.example.com/team/meshbot +# +# Optional: +# PLATFORMS Comma-separated arch list (default: linux/amd64,linux/arm64) +# EXTRA_TAGS Space-separated additional tags to push (e.g. "stable v0.1.0") +# PUSH "1" (default) to push, "0" to build and load locally only +# (note: --load only works with a single platform) +# BUILDER buildx builder name (default: meshbot-builder, auto-created) + +set -euo pipefail + +if [[ -z "${MESHBOT_IMAGE:-}" ]]; then + echo "error: MESHBOT_IMAGE is required (e.g. registry.example.com/team/meshbot)" >&2 + exit 1 +fi + +PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}" +PUSH="${PUSH:-1}" +BUILDER="${BUILDER:-meshbot-builder}" + +cd "$(dirname "$0")/.." + +# Compute git-derived tags. Fall back to "dev" if not in a git tree. +if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + GIT_SHA="$(git rev-parse --short=12 HEAD)" + if [[ -n "$(git status --porcelain)" ]]; then + GIT_SHA="${GIT_SHA}-dirty" + fi +else + GIT_SHA="dev" +fi + +TAGS=(--tag "${MESHBOT_IMAGE}:latest" --tag "${MESHBOT_IMAGE}:${GIT_SHA}") +for t in ${EXTRA_TAGS:-}; do + TAGS+=(--tag "${MESHBOT_IMAGE}:${t}") +done + +# Make sure a buildx builder exists. Reuse if it's already there. +if ! docker buildx inspect "${BUILDER}" >/dev/null 2>&1; then + echo ">>> creating buildx builder '${BUILDER}'" + docker buildx create --name "${BUILDER}" --driver docker-container --use >/dev/null +else + docker buildx use "${BUILDER}" >/dev/null +fi +docker buildx inspect --bootstrap >/dev/null + +OUTPUT_FLAG=() +if [[ "${PUSH}" == "1" ]]; then + OUTPUT_FLAG=(--push) + echo ">>> building & pushing ${MESHBOT_IMAGE} (${PLATFORMS}) tags: latest, ${GIT_SHA}${EXTRA_TAGS:+, $EXTRA_TAGS}" +else + # --load only works with a single platform; warn if user requested multi. + if [[ "${PLATFORMS}" == *,* ]]; then + echo "error: PUSH=0 (local --load) only works with a single platform; got '${PLATFORMS}'" >&2 + exit 1 + fi + OUTPUT_FLAG=(--load) + echo ">>> building ${MESHBOT_IMAGE} for ${PLATFORMS} (local load, no push)" +fi + +docker buildx build \ + --platform "${PLATFORMS}" \ + "${TAGS[@]}" \ + "${OUTPUT_FLAG[@]}" \ + --file Dockerfile \ + . + +echo ">>> done"