Skip to content

[feat] Add Docker support with GHCR publishing#311

Merged
chris-freeman-glean merged 12 commits intomainfrom
cfreeman/docker-improvements
Nov 19, 2025
Merged

[feat] Add Docker support with GHCR publishing#311
chris-freeman-glean merged 12 commits intomainfrom
cfreeman/docker-improvements

Conversation

@chris-freeman-glean
Copy link
Copy Markdown
Contributor

@chris-freeman-glean chris-freeman-glean commented Nov 17, 2025

Overview

Add Docker support with automated multi-architecture image publishing to GitHub Container Registry (GHCR). Docker images are automatically published on releases, providing users with a sandboxed execution environment that abstracts over local environment differences.

Acknowledgments

Special thanks to @aaronsb for the foundational work in #20 on multi-architecture Docker builds and @sholsapp for testing and feedback. This PR builds on those ideas to create a streamlined Docker deployment approach.

Changes

Docker Implementation

  • Dockerfile: Multi-architecture support (amd64/arm64) using node:20-alpine base
  • Non-root user: Runs as uid 1001 for security
  • Package installation: Installs @gleanwork/local-mcp-server from npm at build time

GitHub Actions Workflows

  • publish-docker.yml: Automatically builds and publishes multi-arch images to GHCR on releases
  • docker-test.yml: CI validation workflow that tests Docker image builds on PRs

Documentation

  • Simplified Docker deployment instructions in local-mcp-server README
  • Documents GHCR image usage with clean MCP client configuration examples
  • Updated root README to mention Docker support

Cleanup

  • Removed .dockerignore (no longer needed with npm install approach)

Benefits

  • Multi-architecture: Works on Intel/AMD (amd64) and Apple Silicon (arm64) systems
  • Automated publishing: Images published automatically on release
  • Simple user experience: Users just need docker pull ghcr.io/gleanwork/local-mcp-server:latest
  • Sandboxed execution: Provides isolation and consistent runtime environment
  • Version tagging: Images tagged with semver patterns and latest

Testing

The docker-test workflow validates:

  • Image builds successfully
  • Node.js and npx are available
  • Container starts and runs with stdio transport
  • Security constraints work correctly

Release Process

When running pnpm exec release-it:

  1. Release-it creates a GitHub release with version tag
  2. publish-docker workflow automatically triggers
  3. Multi-arch images are built and pushed to GHCR
  4. Images available at ghcr.io/gleanwork/local-mcp-server with version tags

No additional manual steps required.

- Add Dockerfile for multi-arch Docker image support (amd64/arm64)
- Create GitHub Actions workflow to publish images to GHCR on releases
- Add docker-test workflow for CI validation
- Simplify Docker documentation to use published GHCR images
- Remove .dockerignore (no longer needed with npm install approach)

Docker images will be automatically published to ghcr.io/gleanwork/local-mcp-server
when releases are created, providing users with a sandboxed execution environment
that abstracts over local environment differences.
@chris-freeman-glean chris-freeman-glean force-pushed the cfreeman/docker-improvements branch from 4ab0f86 to d3e6243 Compare November 17, 2025 23:15
chris-freeman-glean and others added 11 commits November 17, 2025 16:18
- Increase test sleep times from 2s to 5s for reliability on slower systems
- Add tmpfs mount for .npm directory in read-only filesystem test
- Add PACKAGE_VERSION build arg to Dockerfile for version pinning
- Document both env block and -e flag approaches for environment variables
- Add comprehensive Docker troubleshooting section to README
- Address container exits, authentication errors, and connection issues
The Dockerfile creates user 'mcpserver' with home at /home/mcpserver,
but the test was mounting tmpfs at /home/mcp/.npm. This caused the
mount to be created at an incorrect path that doesn't match the user's
actual home directory.
Remove -d (detached) flag which conflicts with -i (interactive) for stdio
testing. Detached containers don't have stdin connected, causing MCP servers
to fail or hang when trying to read from unavailable stdin.

New approach:
- Run container in foreground with -i flag and stdin from /dev/null
- Use timeout to prevent hanging
- Run in background with & to allow process checking
- Verify process is running after startup
- Properly cleanup with kill and wait

This properly tests that the server can start and accept stdio communication.
The previous approach had critical flaws:
- $! captured the timeout command PID, not docker
- Killing timeout left docker running uncontrolled in background
- Process checks verified wrong PID
- No actual verification that stdio communication worked

New approach:
- Pipe actual MCP initialize message to container via stdin
- Use 'timeout' as prefix (not wrapper) so docker is the background process
- Capture docker's PID directly with $!
- Use 'kill -0' to check if process is still running (non-destructive)
- Capture output to verify MCP server responds
- Check for JSON-RPC response to confirm stdio communication works
- Proper cleanup with kill and wait
- Show output on failure for debugging

This properly tests that:
1. Container starts successfully
2. Accepts stdin input
3. MCP server can process requests
4. Works with security constraints
The Docker build was failing because npm install -g was being run as
the mcpserver user, which doesn't have permission to write to global
npm directories. This change installs the package as root first, then
switches to the non-root user only for running the server.

This maintains security best practices while fixing the EACCES errors
during the Docker build process.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add production-ready Docker deployment files and enhance CI workflows:

- Add .dockerignore for optimized build context
- Add docker-compose.yaml example in examples/ directory with:
  - Environment variable configuration
  - Resource limits and security options
  - Comprehensive inline documentation
- Enhance publish-docker.yml workflow:
  - Add triggers for main branch and version tags
  - Add id-token write permission for provenance
  - Improve metadata with OCI labels
  - Add branch and SHA tags for better traceability
- Update README with docker-compose reference

These additions provide users with production-grade deployment options
and improved CI/CD automation for multi-arch Docker images.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The MCP server logger needs write access to ~/.local/state/glean
for log files. When running with --read-only, this directory must
be mounted as tmpfs to allow the logger to create its state directory.
Tmpfs mounts are owned by root by default. Since the container runs
as the non-root mcpserver user (uid 1001), we need to specify uid=1001
for the .npm and .local tmpfs mounts so the user can write to them.
This change upgrades the checkout action version to ensure compatibility and access to the latest features and improvements.
@chris-freeman-glean chris-freeman-glean marked this pull request as ready for review November 18, 2025 18:43
@chris-freeman-glean chris-freeman-glean requested a review from a team as a code owner November 18, 2025 18:43
@chris-freeman-glean chris-freeman-glean merged commit 486a0c6 into main Nov 19, 2025
4 checks passed
@chris-freeman-glean chris-freeman-glean deleted the cfreeman/docker-improvements branch November 19, 2025 18:31
@chris-freeman-glean chris-freeman-glean added the enhancement New feature or request label Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants