nandi/frqpublic Fork 0
claude/ci-image-modal-deploy-f0f1d9
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

The registry it pushes to is the one it has 627b785 · on claude/ci-image-modal-deploy-f0f1d9 · nandi · 5h ago
web.yml · 78 lines · 3.1 KBYAML Blame HistoryRaw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# The web bundle: built into an image here, served by Modal from that image.
#
# Separate from `build.yml` because it is a different size of thing. That one
# is three suites and a few minutes; this is a Flutter SDK inside a docker
# build, and it only has to happen for what actually gets served — so it runs
# on the default branch and nowhere else.
#
# The split is by what each side has. rickub has a registry, and docker is
# already logged into it before the first step runs, so this builds and
# pushes. Modal has somewhere to run it, so it pulls that exact tag and
# compiles nothing. What ends up on the internet is the commit that was
# built, by construction rather than by care.
#
# See .modal/web/README.md for the two credentials this needs, and
# https://rickub.com/docs/container-registry for why there is no login step.

name: web

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  image:
    runs-on: ubuntu-latest
    # Claims the image name on the first push. Without it the push is denied
    # for a name that does not exist yet, which reads as an auth failure.
    permissions:
      packages: write
    outputs:
      image: ${{ steps.build.outputs.image }}
    steps:
      - uses: actions/checkout@v4

      # `$RICKUB_REGISTRY_HOST` and the token behind it are injected into the
      # run; docker is authenticated before this step. A `docker login` here
      # would be a second, worse copy of that.
      #
      # Tagged by the full commit sha and not `latest`: the deploy below
      # names the same string, so the thing deployed cannot drift from the
      # thing built. `latest` moves too, as a convenience for a human pulling
      # it by hand.
      - name: Build and push
        id: build
        run: |
          set -euo pipefail
          image="$RICKUB_REGISTRY_HOST/${{ github.repository }}-web:${{ github.sha }}"
          moving="$RICKUB_REGISTRY_HOST/${{ github.repository }}-web:latest"
          docker build -f .modal/web/Dockerfile -t "$image" -t "$moving" .
          docker push "$image"
          docker push "$moving"
          echo "image=$image" >> "$GITHUB_OUTPUT"

  # ...and Modal serves it.
  #
  # `modal deploy` and not `modal run`: a run is a job that ends, and this is
  # a URL that should still be there on the next push. The app is named by
  # `[container] name` in `.modal/web/container.toml`, so deploying again
  # replaces the running one rather than standing a second one beside it.
  deploy:
    runs-on: ubuntu-latest
    needs: [image]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.13"
      - run: pip install --quiet modal
      - name: Deploy
        env:
          MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
          MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
          # What `${FRQ_WEB_IMAGE}` in container.toml expands to. The job
          # above computed it; passing it forward rather than recomputing it
          # keeps one place saying what the tag is.
          FRQ_WEB_IMAGE: ${{ needs.image.outputs.image }}
        run: modal deploy .modal/web/container.py