Skip to content

Commit bdc886a

Browse files
committed
fix(docker): preserve source builds during registry bootstrap
1 parent 4a0dd55 commit bdc886a

6 files changed

Lines changed: 133 additions & 57 deletions

File tree

‎.dockerignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ node_modules
44
**/.next
55
**/.turbo
66
**/dist
7+
**/*.tsbuildinfo
78
.env
89
.env.local
910
.env*.local

‎.github/workflows/docker.yml‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Docker
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "Existing editor release version (without the package prefix)"
8+
required: true
9+
type: string
10+
publish:
11+
description: "Publish to GHCR (otherwise build only)"
12+
default: false
13+
type: boolean
14+
15+
concurrency:
16+
group: docker-publication
17+
cancel-in-progress: false
18+
19+
jobs:
20+
image:
21+
runs-on: ubuntu-latest
22+
permissions:
23+
contents: read
24+
packages: write
25+
steps:
26+
- name: Validate version
27+
env:
28+
VERSION: ${{ inputs.version }}
29+
run: |
30+
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$ ]] || [[ ${#VERSION} -gt 128 ]]; then
31+
echo 'Expected an editor release version suitable for a Docker tag.' >&2
32+
exit 1
33+
fi
34+
35+
- uses: actions/checkout@v4
36+
with:
37+
ref: refs/tags/@pascal-app/editor@${{ inputs.version }}
38+
persist-credentials: false
39+
40+
- name: Verify release version
41+
env:
42+
VERSION: ${{ inputs.version }}
43+
run: jq -e --arg version "$VERSION" '.version == $version' packages/editor/package.json
44+
45+
- uses: docker/setup-qemu-action@v3
46+
47+
- uses: docker/setup-buildx-action@v3
48+
49+
- name: Log in to GHCR
50+
if: inputs.publish
51+
uses: docker/login-action@v3
52+
with:
53+
registry: ghcr.io
54+
username: ${{ github.actor }}
55+
password: ${{ secrets.GITHUB_TOKEN }}
56+
57+
- name: Build versioned image
58+
uses: docker/build-push-action@v6
59+
with:
60+
context: .
61+
platforms: linux/amd64,linux/arm64
62+
push: ${{ inputs.publish }}
63+
tags: ghcr.io/${{ github.repository }}:${{ inputs.version }}
64+
labels: org.opencontainers.image.source=https://github.com/${{ github.repository }}

‎.github/workflows/release.yml‎

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ jobs:
6868
permissions:
6969
contents: write
7070
id-token: write
71-
packages: write
7271
env:
7372
# Verbose npm logs show the OIDC token exchange and the registry's
7473
# rejection reason when trusted publishing is misconfigured; tokens are
@@ -280,48 +279,6 @@ jobs:
280279
echo "📦 Published @pascal-app/editor@$EDITOR_VERSION"
281280
fi
282281
283-
- name: Prepare Docker image tags
284-
if: inputs.package == 'editor' || inputs.package == 'all'
285-
id: docker-tags
286-
env:
287-
BUMP: ${{ inputs.bump }}
288-
run: |
289-
{
290-
echo 'tags<<EOF'
291-
echo "ghcr.io/pascalorg/editor:$EDITOR_VERSION"
292-
case "$BUMP" in
293-
patch|minor|major)
294-
echo "ghcr.io/pascalorg/editor:latest"
295-
;;
296-
esac
297-
echo 'EOF'
298-
} >> "$GITHUB_OUTPUT"
299-
300-
- name: Log in to GHCR
301-
if: (inputs.package == 'editor' || inputs.package == 'all') && inputs.dry-run == false
302-
uses: docker/login-action@v3
303-
with:
304-
registry: ghcr.io
305-
username: ${{ github.actor }}
306-
password: ${{ secrets.GITHUB_TOKEN }}
307-
308-
- name: Set up QEMU
309-
if: inputs.package == 'editor' || inputs.package == 'all'
310-
uses: docker/setup-qemu-action@v3
311-
312-
- name: Set up Docker Buildx
313-
if: inputs.package == 'editor' || inputs.package == 'all'
314-
uses: docker/setup-buildx-action@v3
315-
316-
- name: Build & publish Docker image
317-
if: inputs.package == 'editor' || inputs.package == 'all'
318-
uses: docker/build-push-action@v6
319-
with:
320-
context: .
321-
platforms: linux/amd64,linux/arm64
322-
push: ${{ inputs.dry-run == false }}
323-
tags: ${{ steps.docker-tags.outputs.tags }}
324-
325282
- name: Build & publish nodes
326283
if: inputs.package == 'nodes' || inputs.package == 'all'
327284
working-directory: packages/nodes

‎SETUP.md‎

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,9 @@ Local development and the official hosted editor work without any environment va
3939
docker compose up -d
4040
```
4141

42-
The editor will be running at **http://localhost:3000**. Compose pulls the official
43-
image from GHCR; set `IMAGE_TAG` to use a release instead of `latest`:
44-
45-
```bash
46-
IMAGE_TAG=1.0.0 docker compose up -d
47-
```
48-
49-
The `editor` package must be public in the `pascalorg` organization's GHCR packages
50-
for unauthenticated deployments. After the first release, open the package settings
51-
under the organization's Packages page, set its visibility to **Public**, and connect
52-
it to `pascalorg/editor`. Maintainers only need to configure this once.
53-
54-
Saved scenes live in the `pascal-data` volume, so they survive `docker compose down`.
42+
The editor will be running at **http://localhost:3000**. Compose builds from source;
43+
it does not depend on a GHCR package being available. Saved scenes live in the
44+
`pascal-data` volume, so they survive `docker compose down`.
5545

5646
Docker defaults `MINT_PASCAL_HOST_ORIGIN` to `http://localhost:3000`. Override
5747
it when hosting Pascal at another origin:
@@ -65,6 +55,39 @@ a base URL that only `NEXT_PUBLIC_APP_URL` can override, and Next inlines that
6555
value at build time, so remapping the port to something else makes the page
6656
return 500.
6757

58+
### GHCR bootstrap (maintainers)
59+
60+
Docker publication is separate from the npm release workflow. After a successful
61+
editor release, run the **Docker** workflow with the existing version (for example,
62+
`1.0.0-beta.5`). It checks out `@pascal-app/editor@<version>` and builds amd64 and
63+
arm64 images. Leave `publish` disabled for a build-only check, then enable it to
64+
publish `ghcr.io/<owner>/<repository>:<version>`. It never updates `latest`.
65+
66+
Before switching the default Compose configuration in a follow-up PR:
67+
68+
1. Publish a version from a completed editor release.
69+
2. In the organization's Packages settings, connect the package to `pascalorg/editor`
70+
and set its visibility to **Public** (a one-time maintainer action).
71+
3. Using a Docker configuration without registry credentials, inspect the manifest
72+
and pull both platforms. Replace `<version>` with the published version:
73+
74+
```bash
75+
docker buildx imagetools inspect ghcr.io/pascalorg/editor:<version>
76+
docker pull --platform linux/amd64 ghcr.io/pascalorg/editor:<version>
77+
docker pull --platform linux/arm64 ghcr.io/pascalorg/editor:<version>
78+
```
79+
80+
4. Smoke-test startup and scene persistence on both architectures and record the
81+
version, digest, and results in the follow-up PR. The maintainer must approve
82+
promotion to `latest` and the default-Compose transition separately.
83+
84+
If a Docker build or push fails, npm releases and source-based Compose remain
85+
unchanged. Inspect GHCR for a partially uploaded version before retrying the same
86+
release tag; do not rerun npm publishing or move the Git tag. For a bad image, keep
87+
users on the source build or a previously verified image digest, and publish a
88+
corrected release version rather than silently replacing an image users may have
89+
pinned. Registry cleanup and any future `latest` rollback require maintainer approval.
90+
6891
## CLI-managed editor
6992

7093
Node.js 22.13 or newer can install a persistent local runtime, start it in the

‎docker-compose.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
editor:
3-
image: ghcr.io/pascalorg/editor:${IMAGE_TAG:-latest}
3+
build: .
44
ports:
55
# Keep the container port at 3000. `/scenes` fetches its own API through a
66
# base URL that only `NEXT_PUBLIC_APP_URL` can override, and Next inlines

‎scripts/docker-workflow.test.rb‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
require 'yaml'
2+
require 'open3'
3+
4+
Dir.chdir(File.expand_path('..', __dir__)) do
5+
workflow = YAML.load_file('.github/workflows/docker.yml')
6+
steps = workflow.fetch('jobs').fetch('image').fetch('steps')
7+
validation = steps.find { |step| step['name'] == 'Validate version' }.fetch('run')
8+
{
9+
'1.0.0' => true,
10+
'1.0.0-beta.5' => true,
11+
'main' => false,
12+
'../main' => false,
13+
'1.0.0;echo bad' => false,
14+
'' => false,
15+
"1.0.0-#{'a' * 128}" => false
16+
}.each do |version, expected|
17+
_, status = Open3.capture2e({'VERSION' => version}, 'bash', '-e', '-c', validation)
18+
raise "Incorrect version validation: #{version}" unless status.success? == expected
19+
end
20+
21+
build = steps.find { |step| step['uses'] == 'docker/build-push-action@v6' }.fetch('with')
22+
raise 'Missing architecture' unless build['platforms'] == 'linux/amd64,linux/arm64'
23+
raise 'Publication must be opt-in' unless build['push'] == '${{ inputs.publish }}'
24+
raise 'Do not promote latest during bootstrap' if build['tags'].include?('latest')
25+
checkout = steps.find { |step| step['uses'] == 'actions/checkout@v4' }.fetch('with')
26+
raise 'Build a release tag' unless checkout['ref'] == 'refs/tags/@pascal-app/editor@${{ inputs.version }}'
27+
compose = YAML.load_file('docker-compose.yml').fetch('services').fetch('editor')
28+
raise 'Keep source builds working' unless compose['build'] == '.' && !compose.key?('image')
29+
raise 'Keep npm releases independent' if File.read('.github/workflows/release.yml').include?('docker/')
30+
puts 'PASS: Docker bootstrap workflow checks'
31+
end

0 commit comments

Comments
 (0)