Add Docker build for browser mode with optimized 3-stage build, multi-platform support, comprehensive UI testing, one-click deployment, enterprise SSO integration, and biweekly CI pipeline (#934)
## Docker Build for Browser Solution This PR creates a Docker build for the browser solution (MQTT Explorer server mode). ### Completed: - [x] Create a production Dockerfile for the browser solution (`Dockerfile.browser`) - **NEW**: 3-stage build for maximum optimization - **NEW**: Clean production dependency installation with `yarn --production` - **NEW**: Only compiled dist/ folder copied (no source code) - Alpine Linux base with Node.js 24 - Non-root user (UID 1001) for security - Health check endpoint with proper error handling - Proper signal handling with dumb-init - Production dependencies automatically filtered by yarn - [x] Apply Docker best practices (multi-stage build, minimal image, non-root user, .dockerignore) - Created comprehensive .dockerignore - **FIXED**: Removed events from .dockerignore (needed for build) - **NEW**: Optimized for smaller layers with combined RUN commands - **NEW**: Removed development dependencies from final image - Used alpine base image - [x] Create GitHub Actions workflow for building, publishing, and testing the Docker image - Builds for linux/amd64, linux/arm64, linux/arm/v7 - **FIXED**: Added tsconfig.json and events/** to workflow trigger paths - **FIXED**: Attestation now uses correct digest from build step output - **NEW**: Mosquitto MQTT broker service for integration testing - **NEW**: MQTT broker configurable via MQTT_BROKER_HOST and MQTT_BROKER_PORT environment variables - **NEW**: Full UI test suite runs against containerized application - Tests container startup, health check, HTTP response, data persistence - **NEW**: Image size reporting in workflow summary - Tests verify application works with MQTT broker - Publishes to GitHub Container Registry (ghcr.io/thomasnordquist/mqtt-explorer) - Includes build attestation for supply chain security - [x] Configure workflow to run on push and every two weeks via cron schedule - Runs on 1st and 15th of each month at 2:00 AM UTC - Also runs on push to master/beta/release branches when relevant files change - Manual trigger via workflow_dispatch - [x] Add comprehensive test suite - Basic smoke tests: startup, health check, HTTP response, data persistence - **NEW**: Full UI test suite (`test:browser`) runs against Docker container - **NEW**: Tests connect to configurable MQTT broker (default localhost:1883) - Tests execute with Mosquitto MQTT broker available for backend integration - Same comprehensive tests validate both Electron and browser modes - [x] Test organization and naming - **NEW**: Renamed `test:ui` to `test:electron` for Electron-specific tests - **NEW**: Added `test:browser` script for browser mode tests (runs same UI test suite) - **NEW**: Kept `test:ui` as backward-compatible alias - **NEW**: Renamed `ui-tests` workflow job to `electron-tests` for clarity - [x] Update documentation with Docker usage instructions - Created DOCKER.md with comprehensive Docker documentation - Updated README.md with Docker quick start - **UPDATED**: CI_CD.md now lists all 10 test steps accurately - **NEW**: Added one-click deployment options section - **NEW**: Added authentication modes documentation - [x] **NEW**: One-click deployment solutions - **NEW**: Created docker-compose.yml for easy deployment - **NEW**: Added Play with Docker (PWD) badge for instant browser-based demo - **NEW**: Added DigitalOcean App Platform deployment badge - **NEW**: Added Koyeb deployment badge - **NEW**: Comprehensive deployment options documentation in DOCKER.md - **NEW**: "Try It Now" section in README.md and DOCKER.md with PWD badge - [x] **NEW**: Enterprise authentication integration - **NEW**: Added `MQTT_EXPLORER_SKIP_AUTH` environment variable - **NEW**: Allows disabling built-in authentication for proxy-based auth (OAuth2 Proxy, Authelia, enterprise SSO) - **NEW**: Socket.IO emits `auth-status` event on connection with authentication state - **NEW**: Frontend receives auth status via Socket.IO and skips login dialog when disabled - **NEW**: Logout button hidden when authentication is disabled - **NEW**: Created AuthContext for managing authentication state across components - **NEW**: Comprehensive security warnings in documentation about using skip auth only behind trusted authentication proxies - **NEW**: Updated docker-compose.yml with commented example for proxy authentication - [x] Code review and security scan passed - Fixed health check to handle connection errors properly - Corrected cron schedule comment - No security vulnerabilities found - Fixed image tag naming consistency - Simplified dependency management - **FIXED**: Workflow trigger paths now include all build-affecting files - **FIXED**: Attestation digest reference corrected - **FIXED**: events directory included in Docker build context - **FIXED**: Mosquitto service properly configured for integration testing - **FIXED**: MQTT broker connection now configurable for flexible testing environments - **IMPROVED**: Auth status now communicated via Socket.IO for better real-time synchronization - [x] Rename image to ghcr.io/thomasnordquist/mqtt-explorer (removed -browser suffix) - [x] Add multi-platform support for Raspberry Pi - linux/arm64 (Raspberry Pi 3/4/5) - linux/arm/v7 (Raspberry Pi 2/3) - [x] Upgrade to Node.js 24 (matching project requirements) - [x] **NEW**: Optimize Docker image for minimal size - Only production dependencies (no devDependencies) - No backend source code (only compiled JavaScript) - Removed build tools and dev dependencies - Combined layers for smaller image - **Image size reported in workflow summary** - [x] **NEW**: Fix webpack build configuration - **Enable minification** for production builds (was disabled) - **Update Material-UI references** from @material-ui to @mui - Fix vendor chunking to include @mui and @emotion packages - Reduces bundle size and fixes missing component issues ### Docker Image Features: - **Base**: Alpine Linux with Node.js 24 - **Size**: Reported automatically in workflow summary - **Platforms**: amd64, arm64, arm/v7 (Raspberry Pi support) - **Security**: Non-root user, minimal attack surface - **Reliability**: Health checks, graceful shutdown - **Persistence**: Data volume at `/app/data` - **Registry**: ghcr.io/thomasnordquist/mqtt-explorer - **Runtime deps**: Only production dependencies (automatically filtered) - **Frontend**: Minified webpack bundles with proper vendor splitting - **Testing**: Full UI test suite with configurable MQTT broker integration - **One-Click Deploy**: Play with Docker, DigitalOcean, Koyeb - **Enterprise Ready**: Optional authentication bypass for proxy-based SSO ### Available Tags: - `latest` - Latest stable from master - `master`, `beta`, `release` - Latest from each branch - `<branch>-<sha>` - Specific commits ### Authentication Options: 1. **Standard Mode** (default): Built-in username/password authentication - Set credentials via `MQTT_EXPLORER_USERNAME` and `MQTT_EXPLORER_PASSWORD` environment variables 2. **Skip Authentication Mode**: Set `MQTT_EXPLORER_SKIP_AUTH=true` for proxy-based auth - Use only behind trusted authentication proxies (OAuth2 Proxy, Authelia, enterprise SSO) - Socket.IO automatically informs frontend about auth status on connection - Frontend skips login dialog and logout button is hidden when authentication is disabled - ⚠️ **Security Warning**: Only use in environments with external authentication protection ### One-Click Deployment: Try MQTT Explorer instantly without installation: - **Play with Docker**: Free browser-based demo (click badge in README.md or DOCKER.md) - **DigitalOcean**: Deploy to managed platform starting at $5/month - **Koyeb**: Deploy to global edge network with free tier ### Security Summary: - CodeQL scan passed with no vulnerabilities - Docker image runs as non-root user (UID 1001) - Multi-stage build reduces attack surface - Health check includes proper error handling - Minimal runtime dependencies reduce vulnerability exposure - Full UI test suite validates application functionality - Build attestation with correct digest reference - MQTT broker integration tested with configurable connection via environment variables - Optional authentication bypass for enterprise SSO integration (with comprehensive security warnings) - Auth status communicated via Socket.IO for real-time synchronization <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>create a docker build for the browser solution</issue_title> > <issue_description>Create a docker build for amd64, that ships with a minimal image including nodes. Apply best practices and create a test workflow that builds it , publishes it and tests the built image. Build it every two weeks</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes thomasnordquist/MQTT-Explorer#933 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com> Co-authored-by: Thomas Nordquist <thomasnordquist@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
219
.github/workflows/docker-browser.yml
vendored
Normal file
219
.github/workflows/docker-browser.yml
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
name: Docker Browser Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release
|
||||
- beta
|
||||
paths:
|
||||
- 'Dockerfile.browser'
|
||||
- 'src/server.ts'
|
||||
- 'src/AuthManager.ts'
|
||||
- 'app/**'
|
||||
- 'backend/**'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
- '.github/workflows/docker-browser.yml'
|
||||
- 'tsconfig.json'
|
||||
- 'events/**'
|
||||
schedule:
|
||||
# Run every two weeks (1st and 15th of each month) at 2:00 AM UTC
|
||||
- cron: '0 2 1,15 * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
services:
|
||||
# MQTT broker for testing
|
||||
mosquitto:
|
||||
image: eclipse-mosquitto:2
|
||||
ports:
|
||||
- 1883:1883
|
||||
options: >-
|
||||
--health-cmd "mosquitto_sub -t '$SYS/#' -C 1"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=sha,prefix={{branch}}-
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.browser
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
load: true
|
||||
tags: mqtt-explorer:test
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Test Docker image - Basic startup
|
||||
run: |
|
||||
# Start container with test credentials
|
||||
docker run -d \
|
||||
--name mqtt-explorer-test \
|
||||
-p 3000:3000 \
|
||||
-e MQTT_EXPLORER_USERNAME=test \
|
||||
-e MQTT_EXPLORER_PASSWORD=test123 \
|
||||
-e PORT=3000 \
|
||||
mqtt-explorer:test
|
||||
|
||||
# Wait for server to be ready (max 60 seconds)
|
||||
echo "Waiting for server to start..."
|
||||
for i in {1..60}; do
|
||||
if curl -f http://localhost:3000 > /dev/null 2>&1; then
|
||||
echo "Server started successfully after $i seconds"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 60 ]; then
|
||||
echo "Server failed to start within 60 seconds"
|
||||
docker logs mqtt-explorer-test
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
- name: Test Docker image - Health check
|
||||
run: |
|
||||
# Wait for health check to pass
|
||||
echo "Waiting for health check to pass..."
|
||||
for i in {1..30}; do
|
||||
health=$(docker inspect --format='{{.State.Health.Status}}' mqtt-explorer-test)
|
||||
if [ "$health" = "healthy" ]; then
|
||||
echo "Container is healthy"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 30 ]; then
|
||||
echo "Health check failed"
|
||||
docker logs mqtt-explorer-test
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
- name: Test Docker image - Verify response
|
||||
run: |
|
||||
# Test that the server responds with HTML
|
||||
response=$(curl -s http://localhost:3000)
|
||||
if echo "$response" | grep -q "MQTT Explorer"; then
|
||||
echo "Server is serving the application correctly"
|
||||
else
|
||||
echo "Server response does not contain expected content"
|
||||
echo "Response: $response"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test Docker image - Verify data persistence
|
||||
run: |
|
||||
# Check that data directory was created
|
||||
docker exec mqtt-explorer-test sh -c '[ -d /app/data ] && echo "Data directory exists"'
|
||||
|
||||
- name: Clean up test container
|
||||
if: always()
|
||||
run: |
|
||||
docker stop mqtt-explorer-test || true
|
||||
docker rm mqtt-explorer-test || true
|
||||
|
||||
- name: Check Docker image size
|
||||
run: |
|
||||
echo "### Docker Image Size" >> $GITHUB_STEP_SUMMARY
|
||||
docker images mqtt-explorer:test --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Get size in bytes for detailed reporting
|
||||
SIZE_BYTES=$(docker inspect mqtt-explorer:test --format='{{.Size}}')
|
||||
SIZE_MB=$((SIZE_BYTES / 1024 / 1024))
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Image size**: ${SIZE_MB} MB (${SIZE_BYTES} bytes)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Image size: ${SIZE_MB} MB"
|
||||
|
||||
- name: Setup Node.js for browser tests
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies for browser tests
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Start Docker container for browser tests
|
||||
run: |
|
||||
docker run -d \
|
||||
--name mqtt-explorer-browser-test \
|
||||
--network host \
|
||||
-e MQTT_EXPLORER_USERNAME=test \
|
||||
-e MQTT_EXPLORER_PASSWORD=test123 \
|
||||
-e PORT=3000 \
|
||||
mqtt-explorer:test
|
||||
|
||||
# Wait for server to be ready
|
||||
echo "Waiting for Docker container to be ready..."
|
||||
timeout 60 bash -c 'until curl -f http://localhost:3000; do sleep 1; done'
|
||||
echo "Docker container is ready"
|
||||
|
||||
- name: Run browser test suite
|
||||
run: |
|
||||
yarn test:browser
|
||||
env:
|
||||
MQTT_EXPLORER_USERNAME: test
|
||||
MQTT_EXPLORER_PASSWORD: test123
|
||||
BROWSER_MODE_URL: http://localhost:3000
|
||||
MQTT_BROKER_HOST: localhost
|
||||
MQTT_BROKER_PORT: 1883
|
||||
|
||||
- name: Clean up browser test container
|
||||
if: always()
|
||||
run: |
|
||||
docker logs mqtt-explorer-browser-test || true
|
||||
docker stop mqtt-explorer-browser-test || true
|
||||
docker rm mqtt-explorer-browser-test || true
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.browser
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: ghcr.io/${{ github.repository }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
push-to-registry: true
|
||||
Reference in New Issue
Block a user