diff --git a/.github/workflows/build-devcontainer-bun.yml b/.github/workflows/build-devcontainer-bun.yml index a04ef89..3fc55fe 100644 --- a/.github/workflows/build-devcontainer-bun.yml +++ b/.github/workflows/build-devcontainer-bun.yml @@ -8,18 +8,15 @@ on: - 'devcontainer-bun/.devcontainer/Dockerfile' workflow_dispatch: -env: - REGISTRY: ghcr.io - IMAGE_NAME: devcontainer-bun +concurrency: + group: build-devcontainer-bun-${{ github.ref }} + cancel-in-progress: true jobs: - build-and-push: + prepare: runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - + outputs: + version-tag: ${{ steps.versions.outputs.version-tag }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -28,72 +25,17 @@ jobs: id: versions run: | DOCKERFILE="devcontainer-bun/.devcontainer/Dockerfile" - BUN_VERSION=$(grep '^ARG BUN_VERSION=' "$DOCKERFILE" | cut -d'=' -f2) - - echo "bun=$BUN_VERSION" >> "$GITHUB_OUTPUT" - - echo "Extracted versions:" - echo " Bun: $BUN_VERSION" - - - name: Generate image tags - id: tags - run: | - NAMESPACE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - BASE_IMAGE="${{ env.REGISTRY }}/${NAMESPACE}/${{ env.IMAGE_NAME }}" - - BUN_VERSION="${{ steps.versions.outputs.bun }}" - - VERSION_TAG="bun${BUN_VERSION}-alpine" - - TAGS="${BASE_IMAGE}:latest,${BASE_IMAGE}:${VERSION_TAG}" - - { - echo "namespace=$NAMESPACE" - echo "tags=$TAGS" - } >> "$GITHUB_OUTPUT" - echo "Generated tags:" - echo "$TAGS" | tr ',' '\n' - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ steps.tags.outputs.namespace }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: devcontainer-bun/.devcontainer - file: devcontainer-bun/.devcontainer/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.tags.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Verify image - run: | - TAGS="${{ steps.tags.outputs.tags }}" - FIRST_TAG=$(echo "$TAGS" | cut -d',' -f1) - - echo "Verifying multiplatform image: $FIRST_TAG" - docker pull --platform linux/amd64 "$FIRST_TAG" - docker pull --platform linux/arm64 "$FIRST_TAG" - - echo "Multiplatform image successfully built and pushed!" - echo "Tags: $TAGS" + echo "version-tag=bun${BUN_VERSION}-alpine" >> "$GITHUB_OUTPUT" + echo "Bun: $BUN_VERSION" + + build: + needs: prepare + uses: ./.github/workflows/reusable-docker-build.yml + with: + image-name: devcontainer-bun + context: devcontainer-bun/.devcontainer + dockerfile: devcontainer-bun/.devcontainer/Dockerfile + version-tag: ${{ needs.prepare.outputs.version-tag }} + verify-command: 'bun --version' + secrets: inherit diff --git a/.github/workflows/build-devcontainer-claude-bun.yml b/.github/workflows/build-devcontainer-claude-bun.yml index eb3b80f..0c05ebd 100644 --- a/.github/workflows/build-devcontainer-claude-bun.yml +++ b/.github/workflows/build-devcontainer-claude-bun.yml @@ -9,18 +9,15 @@ on: - 'devcontainer-claude-bun/.devcontainer/init-firewall.sh' workflow_dispatch: -env: - REGISTRY: ghcr.io - IMAGE_NAME: devcontainer-claude-bun +concurrency: + group: build-devcontainer-claude-bun-${{ github.ref }} + cancel-in-progress: true jobs: - build-and-push: + prepare: runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - + outputs: + version-tag: ${{ steps.versions.outputs.version-tag }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -29,72 +26,17 @@ jobs: id: versions run: | DOCKERFILE="devcontainer-claude-bun/.devcontainer/Dockerfile" - BUN_VERSION=$(grep '^ARG BUN_VERSION=' "$DOCKERFILE" | cut -d'=' -f2) - - echo "bun=$BUN_VERSION" >> "$GITHUB_OUTPUT" - - echo "Extracted versions:" - echo " Bun: $BUN_VERSION" - - - name: Generate image tags - id: tags - run: | - NAMESPACE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - BASE_IMAGE="${{ env.REGISTRY }}/${NAMESPACE}/${{ env.IMAGE_NAME }}" - - BUN_VERSION="${{ steps.versions.outputs.bun }}" - - VERSION_TAG="bun${BUN_VERSION}-slim" - - TAGS="${BASE_IMAGE}:latest,${BASE_IMAGE}:${VERSION_TAG}" - - { - echo "namespace=$NAMESPACE" - echo "tags=$TAGS" - } >> "$GITHUB_OUTPUT" - echo "Generated tags:" - echo "$TAGS" | tr ',' '\n' - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ steps.tags.outputs.namespace }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: devcontainer-claude-bun/.devcontainer - file: devcontainer-claude-bun/.devcontainer/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.tags.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Verify image - run: | - TAGS="${{ steps.tags.outputs.tags }}" - FIRST_TAG=$(echo "$TAGS" | cut -d',' -f1) - - echo "Verifying multiplatform image: $FIRST_TAG" - docker pull --platform linux/amd64 "$FIRST_TAG" - docker pull --platform linux/arm64 "$FIRST_TAG" - - echo "Multiplatform image successfully built and pushed!" - echo "Tags: $TAGS" + echo "version-tag=bun${BUN_VERSION}-slim" >> "$GITHUB_OUTPUT" + echo "Bun: $BUN_VERSION" + + build: + needs: prepare + uses: ./.github/workflows/reusable-docker-build.yml + with: + image-name: devcontainer-claude-bun + context: devcontainer-claude-bun/.devcontainer + dockerfile: devcontainer-claude-bun/.devcontainer/Dockerfile + version-tag: ${{ needs.prepare.outputs.version-tag }} + verify-command: 'bun --version' + secrets: inherit diff --git a/.github/workflows/build-devcontainer-hugo-bun-node.yml b/.github/workflows/build-devcontainer-hugo-bun-node.yml index 26c7651..93b0a70 100644 --- a/.github/workflows/build-devcontainer-hugo-bun-node.yml +++ b/.github/workflows/build-devcontainer-hugo-bun-node.yml @@ -8,18 +8,15 @@ on: - 'devcontainer-hugo-bun-node/.devcontainer/Dockerfile' workflow_dispatch: -env: - REGISTRY: ghcr.io - IMAGE_NAME: devcontainer-hugo-bun-node +concurrency: + group: build-devcontainer-hugo-bun-node-${{ github.ref }} + cancel-in-progress: true jobs: - build-and-push: + prepare: runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - + outputs: + version-tag: ${{ steps.versions.outputs.version-tag }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -28,82 +25,19 @@ jobs: id: versions run: | DOCKERFILE="devcontainer-hugo-bun-node/.devcontainer/Dockerfile" - HUGO_VERSION=$(grep '^ARG HUGO_VERSION=' "$DOCKERFILE" | cut -d'=' -f2) BUN_VERSION=$(grep '^ARG BUN_VERSION=' "$DOCKERFILE" | cut -d'=' -f2) NODE_VERSION=$(grep '^ARG NODE_VERSION=' "$DOCKERFILE" | cut -d'=' -f2) - - { - echo "hugo=$HUGO_VERSION" - echo "bun=$BUN_VERSION" - echo "node=$NODE_VERSION" - } >> "$GITHUB_OUTPUT" - - echo "Extracted versions:" - echo " Hugo: $HUGO_VERSION" - echo " Bun: $BUN_VERSION" - echo " Node: $NODE_VERSION" - - - name: Generate image tags - id: tags - run: | - NAMESPACE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - BASE_IMAGE="${{ env.REGISTRY }}/${NAMESPACE}/${{ env.IMAGE_NAME }}" - - HUGO_VERSION="${{ steps.versions.outputs.hugo }}" - BUN_VERSION="${{ steps.versions.outputs.bun }}" - NODE_VERSION="${{ steps.versions.outputs.node }}" - - VERSION_TAG="hugo${HUGO_VERSION}-bun${BUN_VERSION}-node${NODE_VERSION}-alpine" - - TAGS="${BASE_IMAGE}:latest,${BASE_IMAGE}:${VERSION_TAG}" - - { - echo "namespace=$NAMESPACE" - echo "tags=$TAGS" - } >> "$GITHUB_OUTPUT" - echo "Generated tags:" - echo "$TAGS" | tr ',' '\n' - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ steps.tags.outputs.namespace }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: devcontainer-hugo-bun-node/.devcontainer - file: devcontainer-hugo-bun-node/.devcontainer/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.tags.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Verify image - run: | - TAGS="${{ steps.tags.outputs.tags }}" - FIRST_TAG=$(echo "$TAGS" | cut -d',' -f1) - - echo "Verifying multiplatform image: $FIRST_TAG" - docker pull --platform linux/amd64 "$FIRST_TAG" - docker pull --platform linux/arm64 "$FIRST_TAG" - - echo "✅ Multiplatform image successfully built and pushed!" - echo "Tags: $TAGS" + echo "version-tag=hugo${HUGO_VERSION}-bun${BUN_VERSION}-node${NODE_VERSION}-alpine" >> "$GITHUB_OUTPUT" + echo "Hugo: $HUGO_VERSION, Bun: $BUN_VERSION, Node: $NODE_VERSION" + + build: + needs: prepare + uses: ./.github/workflows/reusable-docker-build.yml + with: + image-name: devcontainer-hugo-bun-node + context: devcontainer-hugo-bun-node/.devcontainer + dockerfile: devcontainer-hugo-bun-node/.devcontainer/Dockerfile + version-tag: ${{ needs.prepare.outputs.version-tag }} + verify-command: 'bun --version && hugo version && node --version' + secrets: inherit diff --git a/.github/workflows/build-devcontainer-hugo-bun.yml b/.github/workflows/build-devcontainer-hugo-bun.yml index ef1b515..4cf83fc 100644 --- a/.github/workflows/build-devcontainer-hugo-bun.yml +++ b/.github/workflows/build-devcontainer-hugo-bun.yml @@ -8,18 +8,15 @@ on: - 'devcontainer-hugo-bun/.devcontainer/Dockerfile' workflow_dispatch: -env: - REGISTRY: ghcr.io - IMAGE_NAME: devcontainer-hugo-bun +concurrency: + group: build-devcontainer-hugo-bun-${{ github.ref }} + cancel-in-progress: true jobs: - build-and-push: + prepare: runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - + outputs: + version-tag: ${{ steps.versions.outputs.version-tag }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -28,78 +25,18 @@ jobs: id: versions run: | DOCKERFILE="devcontainer-hugo-bun/.devcontainer/Dockerfile" - HUGO_VERSION=$(grep '^ARG HUGO_VERSION=' "$DOCKERFILE" | cut -d'=' -f2) BUN_VERSION=$(grep '^ARG BUN_VERSION=' "$DOCKERFILE" | cut -d'=' -f2) - - { - echo "hugo=$HUGO_VERSION" - echo "bun=$BUN_VERSION" - } >> "$GITHUB_OUTPUT" - - echo "Extracted versions:" - echo " Hugo: $HUGO_VERSION" - echo " Bun: $BUN_VERSION" - - - name: Generate image tags - id: tags - run: | - NAMESPACE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - BASE_IMAGE="${{ env.REGISTRY }}/${NAMESPACE}/${{ env.IMAGE_NAME }}" - - HUGO_VERSION="${{ steps.versions.outputs.hugo }}" - BUN_VERSION="${{ steps.versions.outputs.bun }}" - - VERSION_TAG="hugo${HUGO_VERSION}-bun${BUN_VERSION}-alpine" - - TAGS="${BASE_IMAGE}:latest,${BASE_IMAGE}:${VERSION_TAG}" - - { - echo "namespace=$NAMESPACE" - echo "tags=$TAGS" - } >> "$GITHUB_OUTPUT" - echo "Generated tags:" - echo "$TAGS" | tr ',' '\n' - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ steps.tags.outputs.namespace }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: devcontainer-hugo-bun/.devcontainer - file: devcontainer-hugo-bun/.devcontainer/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.tags.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Verify image - run: | - TAGS="${{ steps.tags.outputs.tags }}" - FIRST_TAG=$(echo "$TAGS" | cut -d',' -f1) - - echo "Verifying multiplatform image: $FIRST_TAG" - docker pull --platform linux/amd64 "$FIRST_TAG" - docker pull --platform linux/arm64 "$FIRST_TAG" - - echo "✅ Multiplatform image successfully built and pushed!" - echo "Tags: $TAGS" + echo "version-tag=hugo${HUGO_VERSION}-bun${BUN_VERSION}-alpine" >> "$GITHUB_OUTPUT" + echo "Hugo: $HUGO_VERSION, Bun: $BUN_VERSION" + + build: + needs: prepare + uses: ./.github/workflows/reusable-docker-build.yml + with: + image-name: devcontainer-hugo-bun + context: devcontainer-hugo-bun/.devcontainer + dockerfile: devcontainer-hugo-bun/.devcontainer/Dockerfile + version-tag: ${{ needs.prepare.outputs.version-tag }} + verify-command: 'bun --version && hugo version' + secrets: inherit diff --git a/.github/workflows/build-ralphex-fe.yml b/.github/workflows/build-ralphex-fe.yml index 1e390a1..b202d3a 100644 --- a/.github/workflows/build-ralphex-fe.yml +++ b/.github/workflows/build-ralphex-fe.yml @@ -8,20 +8,15 @@ on: - 'ralphex-fe/Dockerfile' workflow_dispatch: -env: - REGISTRY: ghcr.io - IMAGE_NAME: ralphex-fe +concurrency: + group: build-ralphex-fe-${{ github.ref }} + cancel-in-progress: true jobs: - build-and-push: + prepare: runs-on: ubuntu-latest - # Skip if commit was made by the update workflow to avoid double builds - if: ${{ !contains(github.event.head_commit.message, 'via GitHub Actions workflow dispatch') }} - - permissions: - contents: read - packages: write - + outputs: + version-tag: ${{ steps.versions.outputs.version-tag }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -43,90 +38,34 @@ jobs: exit 1 fi - { - echo "bun=$BUN_VERSION" - echo "hugo=$HUGO_VERSION" - echo "ralphex=$RALPHEX_VERSION" - } >> "$GITHUB_OUTPUT" + echo "version-tag=bun${BUN_VERSION}-hugo${HUGO_VERSION}-ralphex${RALPHEX_VERSION}" >> "$GITHUB_OUTPUT" echo "Extracted versions:" echo " Bun: $BUN_VERSION" echo " Hugo: $HUGO_VERSION" echo " Ralphex: $RALPHEX_VERSION" - - name: Generate image tags - id: tags - run: | - NAMESPACE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - BASE_IMAGE="${{ env.REGISTRY }}/${NAMESPACE}/${{ env.IMAGE_NAME }}" - - BUN_VERSION="${{ steps.versions.outputs.bun }}" - HUGO_VERSION="${{ steps.versions.outputs.hugo }}" - RALPHEX_VERSION="${{ steps.versions.outputs.ralphex }}" - - VERSION_TAG="bun${BUN_VERSION}-hugo${HUGO_VERSION}-ralphex${RALPHEX_VERSION}" - - TAGS="${BASE_IMAGE}:latest,${BASE_IMAGE}:${VERSION_TAG}" - - { - echo "namespace=$NAMESPACE" - echo "tags=$TAGS" - } >> "$GITHUB_OUTPUT" - echo "Generated tags:" - echo "$TAGS" | tr ',' '\n' - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ steps.tags.outputs.namespace }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: ralphex-fe - file: ralphex-fe/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.tags.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Verify image - env: - TAGS: ${{ steps.tags.outputs.tags }} - run: | - FIRST_TAG=$(echo "$TAGS" | cut -d',' -f1) - - echo "Verifying multiplatform image: $FIRST_TAG" - docker pull --platform linux/amd64 "$FIRST_TAG" - docker pull --platform linux/arm64 "$FIRST_TAG" - - echo "Verifying Bun installation (amd64)..." - docker run --rm --platform linux/amd64 "$FIRST_TAG" bun --version - - echo "Verifying Hugo installation (amd64)..." - docker run --rm --platform linux/amd64 "$FIRST_TAG" hugo version - - echo "Verifying Bun installation (arm64)..." - docker run --rm --platform linux/arm64 "$FIRST_TAG" bun --version - - echo "Verifying Hugo installation (arm64)..." - docker run --rm --platform linux/arm64 "$FIRST_TAG" hugo version - - echo "Multiplatform image successfully built and pushed!" - echo "Tags: $TAGS" + build: + needs: prepare + uses: ./.github/workflows/reusable-docker-build.yml + with: + image-name: ralphex-fe + context: ralphex-fe + dockerfile: ralphex-fe/Dockerfile + version-tag: ${{ needs.prepare.outputs.version-tag }} + verify-command: 'bun --version && hugo version' + extra-verify-script: | + chromium-browser --no-sandbox --version && \ + test "$PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" = "/usr/bin/chromium-browser" && \ + echo "OK: PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is correct" || \ + (echo "FAIL: expected /usr/bin/chromium-browser, got $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && exit 1) && \ + test -x "$PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && \ + echo "OK: binary is executable at $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" || \ + (echo "FAIL: binary not executable at $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && exit 1) && \ + test "$PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" = "1" && \ + echo "OK: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1" || \ + (echo "FAIL: got PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=$PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" && exit 1) && \ + ls /usr/share/fonts/freefont/FreeSans.ttf && \ + echo "OK: freefont found" || \ + (echo "FAIL: freefont not found" && exit 1) + secrets: inherit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9879676..058c4fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ on: - '.github/workflows/**' - '.hadolint.yaml' +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: # ── Lint Dockerfiles with hadolint ────────────────────────────────── lint-dockerfiles: @@ -123,7 +127,7 @@ jobs: add_image "ralphex-fe" \ "ralphex-fe" \ "ralphex-fe/Dockerfile" \ - "bun --version && hugo version && ralphex --version" + "bun --version && hugo version && /srv/ralphex --version" fi if [ "$INCLUDES" = "[]" ]; then diff --git a/.github/workflows/reusable-docker-build.yml b/.github/workflows/reusable-docker-build.yml new file mode 100644 index 0000000..4037138 --- /dev/null +++ b/.github/workflows/reusable-docker-build.yml @@ -0,0 +1,120 @@ +name: Reusable Docker Build + +on: + workflow_call: + inputs: + image-name: + description: 'Image name (e.g. devcontainer-bun)' + required: true + type: string + context: + description: 'Docker build context path' + required: true + type: string + dockerfile: + description: 'Path to Dockerfile' + required: true + type: string + version-tag: + description: 'Version-specific tag suffix (e.g. bun1.3.5-alpine)' + required: true + type: string + verify-command: + description: 'Tool verification command (e.g. bun --version)' + required: true + type: string + extra-verify-script: + description: 'Additional verification script (e.g. Chromium/Playwright checks)' + required: false + default: '' + type: string + +env: + REGISTRY: ghcr.io + +jobs: + build-and-push: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate image tags + id: tags + run: | + NAMESPACE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + BASE_IMAGE="${{ env.REGISTRY }}/${NAMESPACE}/${{ inputs.image-name }}" + + TAGS="${BASE_IMAGE}:latest,${BASE_IMAGE}:${{ inputs.version-tag }}" + + { + echo "namespace=$NAMESPACE" + echo "tags=$TAGS" + } >> "$GITHUB_OUTPUT" + echo "Generated tags:" + echo "$TAGS" | tr ',' '\n' + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ steps.tags.outputs.namespace }}/${{ inputs.image-name }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ${{ inputs.context }} + file: ${{ inputs.dockerfile }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.tags.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=${{ inputs.image-name }} + cache-to: type=gha,mode=max,scope=${{ inputs.image-name }} + + - name: Verify image + env: + TAGS: ${{ steps.tags.outputs.tags }} + VERIFY_CMD: ${{ inputs.verify-command }} + EXTRA_VERIFY: ${{ inputs.extra-verify-script }} + run: | + FIRST_TAG=$(echo "$TAGS" | cut -d',' -f1) + + echo "Verifying multiplatform image: $FIRST_TAG" + docker pull --platform linux/amd64 "$FIRST_TAG" + docker pull --platform linux/arm64 "$FIRST_TAG" + + echo "Verifying tools (amd64)..." + docker run --rm --platform linux/amd64 --entrypoint sh "$FIRST_TAG" -c "$VERIFY_CMD" + + echo "Verifying tools (arm64)..." + docker run --rm --platform linux/arm64 --entrypoint sh "$FIRST_TAG" -c "$VERIFY_CMD" + + if [ -n "$EXTRA_VERIFY" ]; then + echo "Running extra verification (amd64)..." + docker run --rm --platform linux/amd64 --entrypoint sh "$FIRST_TAG" -c "$EXTRA_VERIFY" + + echo "Running extra verification (arm64)..." + docker run --rm --platform linux/arm64 --entrypoint sh "$FIRST_TAG" -c "$EXTRA_VERIFY" + fi + + echo "Multiplatform image successfully built and pushed!" + echo "Tags: $TAGS" diff --git a/.github/workflows/update-and-build-ralphex-fe.yml b/.github/workflows/update-and-build-ralphex-fe.yml index 3598f78..4016902 100644 --- a/.github/workflows/update-and-build-ralphex-fe.yml +++ b/.github/workflows/update-and-build-ralphex-fe.yml @@ -21,9 +21,11 @@ on: default: false type: boolean +concurrency: + group: build-ralphex-fe-${{ github.ref }} + cancel-in-progress: true + env: - REGISTRY: ghcr.io - IMAGE_NAME: ralphex-fe DOCKERFILE_PATH: ralphex-fe/Dockerfile jobs: @@ -57,6 +59,14 @@ jobs: CURRENT_HUGO=$(grep '^ARG HUGO_VERSION=' "${{ env.DOCKERFILE_PATH }}" | cut -d'=' -f2) CURRENT_RALPHEX=$(grep '^ARG RALPHEX_VERSION=' "${{ env.DOCKERFILE_PATH }}" | cut -d'=' -f2) + if [ -z "$CURRENT_BUN" ] || [ -z "$CURRENT_HUGO" ] || [ -z "$CURRENT_RALPHEX" ]; then + echo "Error: Failed to extract current version(s) from Dockerfile" + echo " Bun: ${CURRENT_BUN:-}" + echo " Hugo: ${CURRENT_HUGO:-}" + echo " Ralphex: ${CURRENT_RALPHEX:-}" + exit 1 + fi + echo "Current versions:" echo " Bun: $CURRENT_BUN" echo " Hugo: $CURRENT_HUGO" @@ -74,9 +84,10 @@ jobs: echo " Ralphex: $NEW_RALPHEX" # Update versions in Dockerfile (GNU sed syntax for Linux) - sed -i "s/^ARG BUN_VERSION=.*/ARG BUN_VERSION=$NEW_BUN/" "${{ env.DOCKERFILE_PATH }}" - sed -i "s/^ARG HUGO_VERSION=.*/ARG HUGO_VERSION=$NEW_HUGO/" "${{ env.DOCKERFILE_PATH }}" - sed -i "s/^ARG RALPHEX_VERSION=.*/ARG RALPHEX_VERSION=$NEW_RALPHEX/" "${{ env.DOCKERFILE_PATH }}" + # Use | as delimiter to avoid breakage if version strings contain / + sed -i "s|^ARG BUN_VERSION=.*|ARG BUN_VERSION=$NEW_BUN|" "${{ env.DOCKERFILE_PATH }}" + sed -i "s|^ARG HUGO_VERSION=.*|ARG HUGO_VERSION=$NEW_HUGO|" "${{ env.DOCKERFILE_PATH }}" + sed -i "s|^ARG RALPHEX_VERSION=.*|ARG RALPHEX_VERSION=$NEW_RALPHEX|" "${{ env.DOCKERFILE_PATH }}" # Verify changes echo "" @@ -114,132 +125,44 @@ jobs: git add "${{ env.DOCKERFILE_PATH }}" - # Create commit with multi-line message using multiple -m flags + # [skip ci] prevents build-ralphex-fe.yml from triggering a duplicate build git commit \ - -m "chore(ralphex-fe): update versions to Bun ${BUN_VERSION}, Hugo ${HUGO_VERSION}, Ralphex ${RALPHEX_VERSION}" \ + -m "chore(ralphex-fe): update versions to Bun ${BUN_VERSION}, Hugo ${HUGO_VERSION}, Ralphex ${RALPHEX_VERSION} [skip ci]" \ -m "Updated via GitHub Actions workflow dispatch." \ -m "Co-Authored-By: ${GITHUB_ACTOR} <${ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com>" git push - echo "✅ Dockerfile updated and pushed to repository" + echo "Dockerfile updated and pushed to repository" - name: No changes needed if: steps.update.outputs.updated == 'false' run: | - echo "ℹ️ Dockerfile already has the requested versions - no update needed" + echo "Dockerfile already has the requested versions - no update needed" build-and-push: needs: update-dockerfile # Build if versions changed and update_only is not checked if: ${{ needs.update-dockerfile.outputs.versions_updated == 'true' && inputs.update_only == false }} - runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - - # Pull latest changes since we just pushed - - name: Pull latest changes - run: git pull - - - name: Extract versions from updated Dockerfile - id: versions - run: | - BUN_VERSION=$(grep '^ARG BUN_VERSION=' "${{ env.DOCKERFILE_PATH }}" | cut -d'=' -f2) - HUGO_VERSION=$(grep '^ARG HUGO_VERSION=' "${{ env.DOCKERFILE_PATH }}" | cut -d'=' -f2) - RALPHEX_VERSION=$(grep '^ARG RALPHEX_VERSION=' "${{ env.DOCKERFILE_PATH }}" | cut -d'=' -f2) - - { - echo "bun=$BUN_VERSION" - echo "hugo=$HUGO_VERSION" - echo "ralphex=$RALPHEX_VERSION" - } >> "$GITHUB_OUTPUT" - - echo "Building with versions:" - echo " Bun: $BUN_VERSION" - echo " Hugo: $HUGO_VERSION" - echo " Ralphex: $RALPHEX_VERSION" - - - name: Generate image tags - id: tags - env: - BUN_VERSION: ${{ steps.versions.outputs.bun }} - HUGO_VERSION: ${{ steps.versions.outputs.hugo }} - RALPHEX_VERSION: ${{ steps.versions.outputs.ralphex }} - run: | - NAMESPACE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - BASE_IMAGE="${{ env.REGISTRY }}/${NAMESPACE}/${{ env.IMAGE_NAME }}" - - VERSION_TAG="bun${BUN_VERSION}-hugo${HUGO_VERSION}-ralphex${RALPHEX_VERSION}" - - TAGS="${BASE_IMAGE}:latest,${BASE_IMAGE}:${VERSION_TAG}" - - { - echo "namespace=$NAMESPACE" - echo "tags=$TAGS" - } >> "$GITHUB_OUTPUT" - echo "Generated tags:" - echo "$TAGS" | tr ',' '\n' - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Container Registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ steps.tags.outputs.namespace }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: ralphex-fe - file: ${{ env.DOCKERFILE_PATH }} - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.tags.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Verify image - env: - TAGS: ${{ steps.tags.outputs.tags }} - run: | - FIRST_TAG=$(echo "$TAGS" | cut -d',' -f1) - - echo "Verifying multiplatform image: $FIRST_TAG" - docker pull --platform linux/amd64 "$FIRST_TAG" - docker pull --platform linux/arm64 "$FIRST_TAG" - - echo "Verifying Bun installation (amd64)..." - docker run --rm --platform linux/amd64 "$FIRST_TAG" bun --version - - echo "Verifying Hugo installation (amd64)..." - docker run --rm --platform linux/amd64 "$FIRST_TAG" hugo version - - echo "Verifying Bun installation (arm64)..." - docker run --rm --platform linux/arm64 "$FIRST_TAG" bun --version - - echo "Verifying Hugo installation (arm64)..." - docker run --rm --platform linux/arm64 "$FIRST_TAG" hugo version - - echo "✅ Multiplatform image successfully built and pushed!" - echo "Tags: $TAGS" + uses: ./.github/workflows/reusable-docker-build.yml + with: + image-name: ralphex-fe + context: ralphex-fe + dockerfile: ralphex-fe/Dockerfile + version-tag: bun${{ needs.update-dockerfile.outputs.bun_version }}-hugo${{ needs.update-dockerfile.outputs.hugo_version }}-ralphex${{ needs.update-dockerfile.outputs.ralphex_version }} + verify-command: 'bun --version && hugo version' + extra-verify-script: | + chromium-browser --no-sandbox --version && \ + test "$PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" = "/usr/bin/chromium-browser" && \ + echo "OK: PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is correct" || \ + (echo "FAIL: expected /usr/bin/chromium-browser, got $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && exit 1) && \ + test -x "$PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && \ + echo "OK: binary is executable at $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" || \ + (echo "FAIL: binary not executable at $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && exit 1) && \ + test "$PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" = "1" && \ + echo "OK: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1" || \ + (echo "FAIL: got PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=$PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD" && exit 1) && \ + ls /usr/share/fonts/freefont/FreeSans.ttf && \ + echo "OK: freefont found" || \ + (echo "FAIL: freefont not found" && exit 1) + secrets: inherit diff --git a/AGENTS.md b/AGENTS.md index b2dd446..c0f585a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,9 @@ When implementing features or making changes, **ALWAYS** check the latest offici - **GitHub Actions**: https://docs.github.com/en/actions - Runs on Ubuntu (GNU/Linux) - use GNU coreutils syntax, NOT macOS/BSD syntax - Example: `sed -i "pattern" file` (GNU) NOT `sed -i.bak "pattern" file` (BSD) + - When substituting version strings with `sed`, use `|` as delimiter instead of `/`: + - Correct: `sed -i "s|^ARG FOO=.*|ARG FOO=$NEW_VERSION|"` — safe with any version string + - Incorrect: `sed -i "s/^ARG FOO=.*/ARG FOO=$NEW_VERSION/"` — breaks if version contains `/` - Check platform-specific tool behavior (sed, grep, awk, etc.) - Verify workflow syntax with official examples @@ -83,11 +86,10 @@ repository-root/ ### Image Tags - Always include `latest` tag -- Version-specific tag format: `{tool}{version}-{variant}` -- Examples: - - `ghcr.io/owner/devcontainer-bun:latest` - - `ghcr.io/owner/devcontainer-bun:bun1.3.5-alpine` - - `ghcr.io/owner/devcontainer-claude-bun:bun1.3.5-slim` +- Devcontainer version-specific tag format: `{tool}{version}-{variant}` + - Examples: `ghcr.io/owner/devcontainer-bun:bun1.3.5-alpine`, `ghcr.io/owner/devcontainer-claude-bun:bun1.3.5-slim` +- Standalone image version-specific tag format: `{primary-version}` (primary tool version only) + - Example: `ghcr.io/owner/ralphex-fe:0.11.0` (ralphex version only; Bun/Hugo versions in README) ## Dockerfile Patterns @@ -114,6 +116,28 @@ Minimal images should include: - `git` - Version control - `zsh` - Better shell for VS Code integration +### Alpine Playwright Support + +When adding Playwright to an Alpine/musl-based image, do NOT use `playwright install` — the bundled Chromium binary requires glibc and is incompatible with Alpine's musl libc. + +Instead: +1. Install system Chromium via apk: `apk add --no-cache chromium ttf-freefont` +2. Set env vars in the Dockerfile (combine into one `ENV` instruction to minimize layers): + ```dockerfile + ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser + ``` +3. In the project's `playwright.config.ts`, wire up `executablePath` manually: + ```typescript + launchOptions: { + executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, + // --disable-dev-shm-usage: prevents Chromium crashes in Docker (default /dev/shm is 64MB) + args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], + } + ``` +4. Projects install only `@playwright/test` (e.g., `bun add -d @playwright/test`) — never `playwright install`. +5. `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` is a project-level convention; Playwright does NOT read it automatically. + ## devcontainer.json Patterns ### File Header diff --git a/README.md b/README.md index 4410c9c..f41f0a7 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Hugo development container with Bun runtime. ### ralphex-fe -Standalone Docker image based on ralphex with Bun 1.3.8 and Hugo Extended 0.155.2 for modern JavaScript/TypeScript development and static site generation. +Standalone Docker image based on ralphex with Bun 1.3.9, Hugo Extended 0.155.3, and Chromium for modern JavaScript/TypeScript development, static site generation, and end-to-end testing. **Usage:** @@ -90,6 +90,7 @@ docker run --rm -v $(pwd):/workspace -w /workspace ghcr.io//ralphex-fe # Run Hugo commands docker run --rm -v $(pwd):/workspace -w /workspace -p 1313:1313 ghcr.io//ralphex-fe:latest hugo server --bind 0.0.0.0 + ``` ## Adding a New Image diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index 650c7b8..b501f95 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -14,12 +14,24 @@ ARG TARGETARCH # Install dependencies: # - ca-certificates: Required for HTTPS connections +# - chromium: System Chromium browser for Playwright tests (Alpine/musl-native, avoids Playwright's +# bundled binary which requires glibc/Debian) +# - ttf-freefont: TrueType fonts required for Chromium text rendering in headless/screenshot mode # - curl: Required by Bun install script # - gcompat: glibc compatibility layer required for Hugo Extended binary (Alpine uses musl) # - go: Required for Hugo Modules to download and manage dependencies (e.g., PaperMod theme) # - unzip: Required by Bun install script on Linux # - wget: Required to download Hugo Extended binary -RUN apk add --no-cache ca-certificates curl gcompat go unzip wget +RUN apk add --no-cache ca-certificates chromium curl gcompat go ttf-freefont unzip wget + +# Playwright environment variables: +# - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: Prevents Playwright's postinstall from attempting to +# download its bundled Chromium binary (which requires glibc and is incompatible with Alpine/musl). +# - PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH: Custom env var that projects can reference in +# playwright.config.ts via executablePath. NOTE: Playwright does NOT read this automatically - +# you must wire it up manually (e.g. executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH). +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser # Install Bun using official install script # BUN_INSTALL sets the installation directory @@ -52,7 +64,7 @@ RUN set -eux; \ # OCI labels for container metadata and GitHub Package integration LABEL org.opencontainers.image.source="https://github.com/gatezh/devcontainer-images" -LABEL org.opencontainers.image.description="Ralphex-fe: Frontend development image with Bun, Hugo Extended, and Ralphex" +LABEL org.opencontainers.image.description="Ralphex-fe: Frontend development image with Bun, Hugo Extended, Chromium, and Ralphex" LABEL org.opencontainers.image.licenses="MIT" LABEL org.opencontainers.image.title="ralphex-fe" LABEL org.opencontainers.image.url="https://github.com/gatezh/devcontainer-images" diff --git a/ralphex-fe/README.md b/ralphex-fe/README.md index 12aa0fc..9be6fb6 100644 --- a/ralphex-fe/README.md +++ b/ralphex-fe/README.md @@ -8,8 +8,9 @@ This is a standalone Docker image, not a devcontainer configuration. It can be u - **Ralphex Base** - Full-featured base image with common development tools - **Node.js** - Included from ralphex base image (version provided by base) -- **Bun 1.3.8** - Fast JavaScript runtime, bundler, and package manager -- **Hugo Extended 0.155.2** - Full-featured static site generator with extended capabilities +- **Bun 1.3.9** - Fast JavaScript runtime, bundler, and package manager +- **Hugo Extended 0.155.3** - Full-featured static site generator with extended capabilities +- **Chromium** - System browser for headless end-to-end testing - **Git** - Version control (included from base) - **Zsh** - Modern shell (included from base) @@ -73,7 +74,7 @@ docker build -t ralphex-fe ralphex-fe/ ```bash docker buildx build \ --platform linux/amd64,linux/arm64 \ - -t ghcr.io//ralphex-fe:bun1.3.8-hugo0.155.2-ralphex \ + -t ghcr.io//ralphex-fe:0.11.0 \ -t ghcr.io//ralphex-fe:latest \ --push \ ralphex-fe @@ -97,15 +98,15 @@ echo $GITHUB_TOKEN | docker login ghcr.io -u --password-stdin ## Image Tags - `latest` - Most recent build -- `bun{version}-hugo{version}-ralphex` - Version-specific tag (e.g., `bun1.3.8-hugo0.155.2-ralphex`) +- `{ralphex-version}` - Version-specific tag (e.g., `0.11.0`) ## Version Information The image uses specific versions defined as build arguments in the Dockerfile: -- **Bun Version**: `1.3.8` (via `BUN_VERSION` build arg) -- **Hugo Version**: `0.155.2` (via `HUGO_VERSION` build arg) -- **Base Image**: `ghcr.io/umputun/ralphex:latest` +- **Bun Version**: `1.3.9` (via `BUN_VERSION` build arg) +- **Hugo Version**: `0.155.3` (via `HUGO_VERSION` build arg) +- **Base Image**: `ghcr.io/umputun/ralphex:0.11.0` (via `RALPHEX_VERSION` build arg) ## License