diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 15da75d..5030eb8 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -16,16 +16,26 @@ env: DOCKER_HUB_USERNAME: ${{ vars.DOCKER_HUB_USERNAME || 'cheffromspace' }} DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION || 'intelligenceassist' }} IMAGE_NAME: ${{ vars.DOCKER_IMAGE_NAME || 'claude-hub' }} + # Runner configuration - set USE_SELF_HOSTED to 'false' to force GitHub-hosted runners + USE_SELF_HOSTED: ${{ vars.USE_SELF_HOSTED || 'true' }} jobs: build: - runs-on: [self-hosted, linux, x64, docker] + # Use self-hosted runners by default, with ability to override via repository variable + runs-on: ${{ fromJSON(format('["{0}"]', (vars.USE_SELF_HOSTED == 'false' && 'ubuntu-latest' || 'self-hosted, linux, x64, docker'))) }} + timeout-minutes: 30 permissions: contents: read packages: write security-events: write steps: + - name: Runner Information + run: | + echo "Running on: ${{ runner.name }}" + echo "Runner OS: ${{ runner.os }}" + echo "Runner labels: ${{ join(runner.labels, ', ') }}" + - name: Checkout repository uses: actions/checkout@v4 @@ -100,8 +110,9 @@ jobs: # Build claudecode separately build-claudecode: - runs-on: [self-hosted, linux, x64, docker] + runs-on: ${{ fromJSON(format('["{0}"]', (vars.USE_SELF_HOSTED == 'false' && 'ubuntu-latest' || 'self-hosted, linux, x64, docker'))) }} if: github.event_name != 'pull_request' + timeout-minutes: 30 permissions: contents: read packages: write @@ -141,4 +152,27 @@ jobs: tags: ${{ steps.meta-claudecode.outputs.tags }} labels: ${{ steps.meta-claudecode.outputs.labels }} cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + cache-to: type=gha,mode=max + + # Fallback job if self-hosted runners timeout + build-fallback: + needs: [build, build-claudecode] + if: | + always() && + (needs.build.result == 'failure' || needs.build-claudecode.result == 'failure') && + vars.USE_SELF_HOSTED != 'false' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + security-events: write + + steps: + - name: Trigger rebuild on GitHub-hosted runners + run: | + echo "Self-hosted runner build failed. To retry with GitHub-hosted runners:" + echo "1. Set the repository variable USE_SELF_HOSTED to 'false'" + echo "2. Re-run this workflow" + echo "" + echo "Or manually trigger a new workflow run with GitHub-hosted runners." + exit 1 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index dbbcd3a..c4bc9b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -88,24 +88,25 @@ RUN groupadd -g 999 docker 2>/dev/null || true \ && useradd -m -u 1001 -s /bin/bash claudeuser \ && usermod -aG docker claudeuser 2>/dev/null || true -# Create npm global directory for claudeuser and set permissions +# Create necessary directories and set permissions while still root RUN mkdir -p /home/claudeuser/.npm-global \ - && chown -R claudeuser:claudeuser /home/claudeuser/.npm-global + && mkdir -p /home/claudeuser/.config/claude \ + && chown -R claudeuser:claudeuser /home/claudeuser/.npm-global /home/claudeuser/.config # Configure npm to use the user directory for global packages -USER claudeuser ENV NPM_CONFIG_PREFIX=/home/claudeuser/.npm-global ENV PATH=/home/claudeuser/.npm-global/bin:$PATH +# Switch to non-root user and install Claude Code +USER claudeuser + # Install Claude Code (latest version) as non-root user # hadolint ignore=DL3016 RUN npm install -g @anthropic-ai/claude-code +# Switch back to root for remaining setup USER root -# Create claude config directory -RUN mkdir -p /home/claudeuser/.config/claude - WORKDIR /app # Copy production dependencies from prod-deps stage diff --git a/docs/docker-optimization.md b/docs/docker-optimization.md index ea058a7..7031886 100644 --- a/docs/docker-optimization.md +++ b/docs/docker-optimization.md @@ -16,22 +16,38 @@ Our optimized Docker build pipeline includes: ### Configuration - **Labels**: `self-hosted, linux, x64, docker` -- **Usage**: All Docker builds use self-hosted runners for improved performance and caching +- **Usage**: All Docker builds use self-hosted runners by default for improved performance - **Local Cache**: Self-hosted runners maintain Docker layer cache between builds -- **Fallback**: Manual fallback to GitHub-hosted runners if self-hosted are unavailable +- **Fallback**: Configurable via `USE_SELF_HOSTED` repository variable ### Runner Setup Self-hosted runners provide: - Persistent Docker layer cache -- Faster builds (no image pull overhead) +- Faster builds (no image pull overhead) - Better network throughput for pushing images - Cost savings on GitHub Actions minutes ### Fallback Strategy -If self-hosted runners are unavailable: -1. Workflow will queue waiting for runners -2. Can be manually cancelled and re-run with modified workflow -3. Consider implementing automatic fallback in future iterations +The workflow implements a flexible fallback mechanism: + +1. **Default behavior**: Uses self-hosted runners (`self-hosted, linux, x64, docker`) +2. **Override option**: Set repository variable `USE_SELF_HOSTED=false` to force GitHub-hosted runners +3. **Timeout protection**: 30-minute timeout prevents hanging on unavailable runners +4. **Failure detection**: `build-fallback` job provides instructions if self-hosted runners fail + +To manually switch to GitHub-hosted runners: +```bash +# Via GitHub UI: Settings → Secrets and variables → Actions → Variables +# Add: USE_SELF_HOSTED = false + +# Or via GitHub CLI: +gh variable set USE_SELF_HOSTED --body "false" +``` + +The runner selection logic: +```yaml +runs-on: ${{ fromJSON(format('["{0}"]', (vars.USE_SELF_HOSTED == 'false' && 'ubuntu-latest' || 'self-hosted, linux, x64, docker'))) }} +``` ## Multi-Stage Dockerfile