diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index e797abd..4833a39 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -13,13 +13,22 @@ env: IMAGE_NAME: ${{ github.repository }} jobs: - build-and-push-frontend: - runs-on: ubuntu-latest + build-frontend: + runs-on: ${{ matrix.runner }} permissions: contents: read packages: write id-token: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -35,6 +44,66 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend + + - name: Build and push Docker image by digest + id: build + uses: docker/build-push-action@v5.0.0 + with: + context: ./frontend + platforms: ${{ matrix.platform }} + push: ${{ github.event_name != 'pull_request' }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=frontend-${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=frontend-${{ matrix.platform }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} + + - name: Export digest + if: github.event_name != 'pull_request' + run: | + mkdir -p /tmp/digests/frontend + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/frontend/${digest#sha256:}" + + - name: Upload digest + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: digests-frontend-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }} + path: /tmp/digests/frontend/* + if-no-files-found: error + retention-days: 1 + + merge-frontend: + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + needs: build-frontend + permissions: + contents: read + packages: write + + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests/frontend + pattern: digests-frontend-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5.0.0 @@ -45,25 +114,29 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable={{is_default_branch}} - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v5.0.0 - with: - context: ./frontend - platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Create and push manifest + working-directory: /tmp/digests/frontend + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend@sha256:%s ' *) - build-and-push-backend: - runs-on: ubuntu-latest + build-backend: + runs-on: ${{ matrix.runner }} permissions: contents: read packages: write id-token: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -79,6 +152,66 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backend + + - name: Build and push Docker image by digest + id: build + uses: docker/build-push-action@v5.0.0 + with: + context: ./backend + platforms: ${{ matrix.platform }} + push: ${{ github.event_name != 'pull_request' }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=backend-${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=backend-${{ matrix.platform }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backend,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} + + - name: Export digest + if: github.event_name != 'pull_request' + run: | + mkdir -p /tmp/digests/backend + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/backend/${digest#sha256:}" + + - name: Upload digest + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: digests-backend-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }} + path: /tmp/digests/backend/* + if-no-files-found: error + retention-days: 1 + + merge-backend: + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + needs: build-backend + permissions: + contents: read + packages: write + + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests/backend + pattern: digests-backend-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5.0.0 @@ -89,14 +222,9 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable={{is_default_branch}} - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v5.0.0 - with: - context: ./backend - platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Create and push manifest + working-directory: /tmp/digests/backend + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backend@sha256:%s ' *) diff --git a/.gitignore b/.gitignore index ca4993a..87dc843 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,6 @@ clean_zip.py zip_repo.py refactor_cesium.py jobs.json + +.claude +.mise.local.toml diff --git a/backend/Dockerfile b/backend/Dockerfile index 225f635..8616154 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -9,16 +9,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* -# Install dependencies +# Install Python dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt +# Install Node.js dependencies (ws module for AIS WebSocket proxy) +# Copy manifests first so this layer is cached unless deps change +COPY package*.json ./ +RUN npm install --omit=dev + # Copy source code COPY . . -# Install Node.js dependencies (ws module for AIS WebSocket proxy) -RUN npm install --omit=dev - # Create a non-root user for security RUN adduser --system --uid 1001 backenduser \ && chown -R backenduser /app