feat: Dockerfile + ghcr publish workflow#16
Conversation
Adds a multi-stage Dockerfile that builds mentisdbd in a rust:1.83-bookworm builder stage and ships it on debian:bookworm-slim. Runtime image includes libasound2 (alsa-sys link), libssl3, ca-certificates, and tini for clean PID 1 / signal handling. Runs as a non-root `mentisdb` user (uid/gid 991) with `/var/lib/mentisdb` as the data volume. Default env exposes the daemon's MCP (9471), REST (9472), and dashboard (9475) on 0.0.0.0 inside the container; bind to 127.0.0.1 by setting `MENTISDB_BIND_HOST` if you want a sidecar reverse-proxy pattern. The `.github/workflows/docker-publish.yml` workflow builds multi-arch (linux/amd64 + linux/arm64) images via Buildx + QEMU and pushes to `ghcr.io/<repo>/mentisdb:<version>` plus `:latest` on every daemon version tag (PascalCase semver) or via workflow_dispatch. Cache via GitHub Actions cache for fast iteration. Provenance + SBOM attestation enabled. Run with `docker run -dt ...`. The `-t` flag is currently required because mentisdbd opens `/dev/tty` for TUI init even when run non-interactively; a `--headless` flag upstream would remove this requirement. Why: building mentisdbd from source via `cargo install` takes ~10-15 minutes on a 2-core/4GB cloud VM (cold cargo cache). A pre-built image lets operators provision in seconds, makes mentisdbd usable in CI smoke tests / integration suites without burning compile time, and gives a stable reference for downstream packaging. Closes-Hint: I'm running mentisdbd in production and have rebuilt the host VM 7 times in the last 4 hours iterating on cloud-init scripts. A docker image would have saved ~70 minutes of compile time.
There was a problem hiding this comment.
Pull request overview
Adds first-class containerization for mentisdbd and automated publishing of multi-arch images to GitHub Container Registry on version tag pushes, enabling much faster operator/CI startup versus compiling from source.
Changes:
- Introduces a multi-stage
Dockerfileto buildmentisdbdand run it as a non-root user ondebian:bookworm-slim. - Adds a GitHub Actions workflow to build/push
linux/amd64+linux/arm64images to GHCR with:latestand version tags. - Adds a
.dockerignoreto keep Docker build context small.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
Dockerfile |
Multi-stage build (Rust builder → Debian runtime) with non-root runtime defaults for mentisdbd. |
.github/workflows/docker-publish.yml |
Tag-triggered buildx workflow to publish versioned + latest images to GHCR. |
.dockerignore |
Excludes large/non-essential paths from the Docker build context to speed builds. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Resolve version | ||
| id: ver | ||
| run: | | ||
| if [ -n "${{ inputs.version }}" ]; then | ||
| echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT" | ||
| else | ||
| # Strip leading v from tag refs like refs/tags/v0.9.5 | ||
| ref="${{ github.ref_name }}" | ||
| echo "version=${ref#v}" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
| on: | ||
| push: | ||
| tags: | ||
| # Only daemon-version tags (PascalCase semver). Excludes pymentisdb-v*. | ||
| - '[0-9]+.[0-9]+.[0-9]+' | ||
| - 'v[0-9]+.[0-9]+.[0-9]+' | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: 'Version tag to publish (e.g. 0.9.5)' | ||
| required: true | ||
| type: string | ||
|
|
| README.md | ||
| ROADMAP.md | ||
| ENGINEERING_PIPELINE.md | ||
| MENTISDB_SKILL.md |
| - '[0-9]+.[0-9]+.[0-9]+' | ||
| - 'v[0-9]+.[0-9]+.[0-9]+' |
|
|
||
| env: | ||
| REGISTRY: ghcr.io | ||
| IMAGE_NAME: ${{ github.repository_owner }}/mentisdb |
…r edition2024 Six issues found: 1. Bot CloudLLM-ai#3 (CRITICAL): .dockerignore was excluding MENTISDB_SKILL.md but src/server.rs and src/bin/mentisdbd.rs embed it via include_str!. Removed the entry; documented why it can't be re-added. 2. Bot CloudLLM-ai#5: IMAGE_NAME used `${{ github.repository_owner }}` which can be mixed-case (e.g. CloudLLM-ai). Docker/OCI refs require lowercase. Compute lowercase image name in a step output. 3. Bot CloudLLM-ai#4: tag patterns used regex syntax (`[0-9]+`). GH Actions tag filters are glob, not regex. Switched to `[0-9]*.[0-9]*.[0-9]*` so tags actually trigger the workflow. 4. Bot CloudLLM-ai#1: workflow_dispatch with `version=X` was building from default branch HEAD, not from the tag X. Added an inputs.ref defaulting to refs/tags/<version> so dispatched builds match the tag content. Operator can override via inputs.ref. 5. Bot CloudLLM-ai#2: PR description claimed PR validation but workflow only ran on tag pushes. Added pull_request trigger that builds amd64 only, no push, used as a smoke test on Dockerfile/workflow/source changes. 6. Edition2024 (caught by failing test build on fork): rust:1.83 can't parse Cargo.toml of transitive deps that require edition2024 (e.g. line-clipping 0.3.7). Bumped builder to rust:1.85-bookworm.
|
Pushed fixes for all 5 review comments + a 6th issue caught by my fork's failing test build:
Force-reverification on my fork now in progress; I'll report a green tag-build before recommending merge. |
Replaces the in-vm cargo install of mentisdbd with a docker pull + systemd unit wrapping `docker run`. Cuts cloud-init bootstrap from ~10-15 min cargo compile to <1 min image pull + container start. Variable rename: var.mentisdb_version (crates.io semver) -> var.mentisdb_image (full container ref including tag). Default points at the operator's fork build (ghcr.io/nycterent/mentisdb:0.9.5-test4) while CloudLLM-ai/mentisdb#16 is pending merge. Once upstream PR lands and tags an image at ghcr.io/cloudllm-ai/mentisdb:0.9.5, swap the default. Container internals: - Runs as uid 991 (mentisdb user baked into image) - /var/lib/mentisdb mounted from host (uid 991 owned) - 9471/9472/9475 published to 127.0.0.1 only; nginx host-side proxies HTTPS:443 -> 127.0.0.1:9472 - -t allocates pseudo-TTY for /dev/tty probe; image baked-in tini handles SIGTERM cleanly Drops: - rustup-init pinned download + sha256 verification - cargo install with libasound2-dev, build-essential, pkg-config, libssl-dev compile-time deps - mentisdb system user creation (lives inside the container now) - /etc/mentisdb/mentisdbd.env (env vars passed via docker -e) - script -qfc PTY workaround (-t flag in docker run replaces it) Keeps: - nginx + Basic Auth + certbot reverse proxy on the host - ufw firewall - shred of /var/lib/cloud/instance/user-data.txt post-htpasswd - Newline/CRLF rejection on basic_auth_password
|
Pushed missing commit ( Verified end-to-end:
Ready for maintainer review. |
|
Hey @nycterent can't believe I hadn't noticed your PR. Thank you so much for all this work, I'll get on it soon. |
TL;DR — Conditional merge. Fix three things first.The intent and rationale are sound, the workflow is competently written, and the security posture is mostly good. But the PR was built/tested against a stale fork and contains a fatal build bug plus two smaller gaps. If those are fixed, this is a clear net positive for the project. 1. Showstopper — wrong binary name (PR is broken on master)Cargo.toml:100-103 declares: The PR's Dockerfile runs:
The "test plan" in the PR description (CI builds green) cannot have been verified against the current master of this repo — the author was running against their own stale fork that still had the mentisdbd` name. Fix: 2. The --headless flag already exists — the PR is wrong about needing it as a follow-up
The PR description says:
That's a stale claim. The right entrypoint is:
…and the documented 3. Tag patterns are too loose ([0-9].[0-9].[0-9]*)GitHub Actions tag patterns are glob; * matches any character including .. So:
In practice the only realistic collision vector is your own future tags. If you ever tag mentisdb-v0.9.5 or pymentisdb-0.9.5 the workflow will try to build a Docker image from it. A safer regex-style glob would be Not blocking, but worth tightening. 4. Security & Safety, a few gaps
Merge recommendationConditional yes. Please:
Nice-to-haves that I'd accept in a follow-up PR, not block on:
|
Summary
Adds a multi-stage Dockerfile for mentisdbd plus a GitHub Actions workflow that publishes versioned + latest images to ghcr.io on every daemon version tag.
Why
Building mentisdbd from source via
cargo installtakes ~10-15 minutes on a 2-core/4GB cloud VM. A pre-built image lets operators provision in seconds, makes mentisdbd usable in CI smoke tests without burning compile time, and gives downstream packagers a stable reference.Image design
rust:1.83-bookwormwithpkg-config libssl-dev libasound2-dev(alsa-sys + reqwest TLS link).cargo install --lockedfor reproducibility.debian:bookworm-slimwithlibasound2 libssl3 ca-certificates tini. Non-rootmentisdbuser (uid 991)./var/lib/mentisdbis the named volume.MENTISDB_BIND_HOST=127.0.0.1for sidecar-proxy patterns), audio features off,RUST_LOG=info.Run
-t(allocate pseudo-TTY) is currently required — mentisdbd 0.9.5 opens/dev/ttyfor TUI init even when running non-interactively, exiting with ENXIO if no controlling terminal is available. A--headlessflag (or graceful TUI fallback when isatty(0)/isatty(1) are false AND /dev/tty open fails) would let the image run without-t. Happy to follow up with a PR for that if maintainers prefer.Workflow
Triggers on daemon-version tags (PascalCase semver:
0.9.5,v0.9.5) — does NOT collide with the existingpymentisdb-v*PyPI publish trigger. Multi-arch (linux/amd64 + linux/arm64) via Buildx + QEMU. Pushes toghcr.io/$GITHUB_REPOSITORY_OWNER/mentisdb. Uses gha cache, attests provenance + SBOM.Test plan
ghcr.io/cloudllm-ai/mentisdb:<version>and:latestdocker run -dt --rm ghcr.io/cloudllm-ai/mentisdb:<version>starts the daemon and listens on 9471/9472/9475curl http://localhost:9472/v1/agents -X POST -d '{}' -H 'Content-Type: application/json'returns 200Notes