feat(docker): publish container image to ghcr.io/esnunes/destila - #148
Open
esnunes wants to merge 6 commits into
Open
feat(docker): publish container image to ghcr.io/esnunes/destila#148esnunes wants to merge 6 commits into
esnunes wants to merge 6 commits into
Conversation
Lays out a six-unit plan for shipping an official Destila container image to ghcr.io/esnunes/destila: release scaffold via mix phx.gen.release, a multi-stage Dockerfile that bundles claude/tmux/ffmpeg/agent-browser/git, a GHCR publish workflow, a README "Run with Docker" section documenting ~/.claude and ~/.cache/destila mounts, and a clean-machine smoke test. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a multi-stage Dockerfile that builds a mix release and layers every CLI Destila.Deps checks for (claude, tmux, ffmpeg, agent-browser, git) onto a debian-bookworm-slim runtime. Chromium is installed alongside agent-browser so headless browser skills work out of the box. The container auto-migrates on boot via Destila.Release.migrate/0 and supports three bind mounts: ~/.claude for Claude CLI state, ~/.cache/destila for per-project clones and worktrees, and /data for the SQLite DB. A new .github/workflows/docker-publish.yml pushes the image to GHCR on main pushes and v*.*.* tags with GHA layer caching. README gains a "Run with Docker" section that walks through pull, SECRET_KEY_BASE generation, the three auth options, the canonical docker run invocation, volumes reference, and troubleshooting. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove dead rel/overlays/bin/server{,.bat} — the entrypoint calls
bin/destila start directly, never through these overlays.
- Drop unused id-token: write from the GHCR publish workflow; we do
not sign or attest with OIDC.
- Note the Dockerfile coupling at Destila.Deps.@required_tools so
future additions do not silently break the container image.
- Soften the --user docker run recommendation to flag volume-ownership
EACCES risk instead of claiming it works out of the box.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add docker/setup-qemu-action to register arm64 emulation, then build and push for both linux/amd64 and linux/arm64. The resulting GHCR tag is a multi-arch manifest, so Apple Silicon and arm64 servers pull a native image without docker run --platform overrides. Update the README Limitations subsection to reflect that ARM hosts are now first-class. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch both Dockerfile stages from the hexpm/elixir base image to plain
debian:bookworm-slim and let mise resolve erlang, elixir, and node from
mise.toml — the same source of truth used in dev. This also installs
ffmpeg and tmux from mise in the runtime stage instead of apt.
What stays apt:
- chromium (no mise plugin)
- ncurses/libstdc++/openssl (BEAM links to them at runtime)
- tini (PID 1), locales (system), ca-certificates, curl, git
- build-essential / autoconf / m4 / unzip / libssl-dev / libncurses-dev
in the build stage so kerl can compile OTP
What moves to mise:
- erlang, elixir (was: hexpm/elixir base image)
- node (was: apt nodejs/npm)
- ffmpeg, tmux (was: apt)
Tradeoff: building OTP from source via kerl adds ~5–10 minutes per arch
to the CI build relative to the pre-built hexpm image. GHA build cache
amortizes this across pushes that don't change mise.toml. Single source
of truth wins over CI runtime here.
Also tighten mise.toml syntax (drop the redundant `{ version = "..." }`
wrapper for erlang/elixir, add node@22) and update the Destila.Deps
coupling comment to reference the mise/apt split.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch the runtime from a `mix release` start script to running
`elixir --sname destila -S mix phx.server` directly against the
compiled source tree, with output redirected to
$HOME/.cache/destila/services/project-destila-main.log so the start
command matches how Destila launches its own managed services on a
host.
Build stage:
- Drops `mix release`; adds `mix compile` + `mix assets.deploy`
against MIX_ENV=prod.
Runtime stage:
- Copies the compiled /app tree (lib, _build/prod, deps, priv,
config, mix.exs/mix.lock) and /mise (erlang, elixir, node) from
the build stage instead of unpacking a release tarball.
- Entrypoint moved to /app/entrypoint.sh, sourced from
docker/entrypoint.sh in the build context.
Cleanup:
- Delete `lib/destila/release.ex`, `rel/overlays/bin/migrate`,
and `rel/overlays/bin/migrate.bat` — all release-specific and
no longer reachable.
- Move `rel/overlays/bin/entrypoint.sh` to `docker/entrypoint.sh`
so the path reflects the actual deployment vehicle.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ghcr.io/esnunes/destilavia a multi-stageDockerfilethat compiles a productionmix releaseand ships it alongside every required CLI (claude,tmux,ffmpeg,agent-browser,git) plus Chromium foragent-browser.mainpush andv*.*.*tag via.github/workflows/docker-publish.ymlusingdocker/build-push-action@v5with semver +latest+ short-sha tags and GHA build cache.lib/destila/release.ex,rel/overlays/bin/entrypoint.sh(runs migrations thenbin/destila startundertini), and migrate overlays. SQLite DB lives on the/datavolume (DATABASE_PATH=/data/destila.db);/root/.claudeand/root/.cache/destilaare also declaredVOLUMEs so workflow worktrees and Claude login survive recreation.README.md— pull, auth options, volume mounts, limitations (linux/amd64 only, root by default, dynamic service ports), and troubleshooting.Destila.Deps.@required_toolsto the Dockerfile runtime stage via a comment so adding a tool doesn't silently break the image.Review
ce:review(headless, 8 reviewers) returned no P0/P1 findings. Applied 4 P2 autofixes inaf69c65:id-token: writefrom the workflow (no OIDC signing).rel/overlays/bin/server{,.bat}— the entrypoint callsbin/destila startdirectly.--user $(id -u):$(id -g)recommendation in the README to flag volume-ownership EACCES risk.@required_tools↔ Dockerfile coupling note.Verification
mix precommit— 739 tests, 0 failures;--warnings-as-errorsclean; format clean.mix releaseproduces a working release bundle locally.Post-Deploy Monitoring & Validation
Publish Docker imageworkflow in Actions → confirm themainpush produces bothghcr.io/esnunes/destila:mainandghcr.io/esnunes/destila:latest.Destila.Deps.check/0reportsavailable?: truefor all tools.v0.1.0tag and confirm semver tags (0.1.0,0.1,0) land on GHCR.available?: falsein production, or migrations fail on boot. Mitigation: revert to the prior image tag.Test plan
Publish Docker imageworkflow succeeds on the firstmainpush after merge.docker pull ghcr.io/esnunes/destila:latestworks from a logged-out machine (public image visibility).docker runper README step 4 boots, migrates, and serves http://localhost:4000.v0.1.0) produces the expected0.1.0,0.1,0, andlatesttags.🤖 Generated with Claude Code