Skip to content

Commit 0789c79

Browse files
authored
Merge branch 'main' into fix/pty-spawn-timeout
2 parents 281437a + 5f293a1 commit 0789c79

3 files changed

Lines changed: 180 additions & 5 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
name: Test Docker Image
2+
3+
permissions: {}
4+
5+
on:
6+
# Manual runs can pin a specific vp version or verify a PR's registry-bridge
7+
# build. Leave both blank to use the Dockerfile defaults (VP_VERSION=latest).
8+
workflow_dispatch:
9+
inputs:
10+
vp_version:
11+
description: 'vp version or dist-tag to install (blank = Dockerfile default: latest)'
12+
required: false
13+
type: string
14+
vp_pr_version:
15+
description: 'PR number or commit SHA to build a preview image from the registry bridge (overrides vp_version)'
16+
required: false
17+
type: string
18+
pull_request:
19+
paths:
20+
- 'docker/**'
21+
- '.github/workflows/test-docker-image.yml'
22+
23+
concurrency:
24+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
25+
cancel-in-progress: true
26+
27+
defaults:
28+
run:
29+
shell: bash
30+
31+
jobs:
32+
build-and-smoke-test:
33+
name: Build and smoke-test the toolchain image
34+
runs-on: ubuntu-latest
35+
timeout-minutes: 20
36+
permissions:
37+
contents: read
38+
env:
39+
IMAGE: vite-plus:ci-smoke
40+
steps:
41+
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
42+
43+
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
44+
45+
# Only forward build-args that were provided (manual runs), so
46+
# empty inputs fall back to the Dockerfile defaults instead of overriding
47+
# them with empty strings.
48+
- name: Resolve build args
49+
id: buildargs
50+
env:
51+
VP_VERSION: ${{ inputs.vp_version }}
52+
VP_PR_VERSION: ${{ inputs.vp_pr_version }}
53+
run: |
54+
{
55+
echo "args<<__EOF__"
56+
if [ -n "$VP_VERSION" ]; then echo "VP_VERSION=$VP_VERSION"; fi
57+
if [ -n "$VP_PR_VERSION" ]; then echo "VP_PR_VERSION=$VP_PR_VERSION"; fi
58+
echo "__EOF__"
59+
} >> "$GITHUB_OUTPUT"
60+
61+
# Build the toolchain image locally and load it into the daemon (no push).
62+
# amd64-only keeps it fast; the release build covers arm64.
63+
- name: Build image
64+
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
65+
with:
66+
context: docker
67+
file: docker/Dockerfile
68+
platforms: linux/amd64
69+
load: true
70+
push: false
71+
tags: ${{ env.IMAGE }}
72+
build-args: ${{ steps.buildargs.outputs.args }}
73+
provenance: false
74+
75+
# Guard the security/DX balance the image commits to: it stays non-root by
76+
# default, yet the vp user can reach root through sudo. The apt-get check is
77+
# the exact escalation `playwright install --with-deps` performs, so this
78+
# would have caught the missing-sudo failure from issue #2087.
79+
- name: Smoke test
80+
run: |
81+
docker run --rm "$IMAGE" sh -euc '
82+
echo "== default user is the non-root vp (uid 1000) =="
83+
user=$(whoami); uid=$(id -u)
84+
echo "user=$user uid=$uid"
85+
[ "$user" = vp ] || { echo "FAIL: expected default user vp, got $user"; exit 1; }
86+
[ "$uid" = 1000 ] || { echo "FAIL: expected uid 1000, got $uid"; exit 1; }
87+
88+
echo "== sudo present so playwright install --with-deps takes the sudo branch =="
89+
command -v sudo >/dev/null || { echo "FAIL: sudo not on PATH"; exit 1; }
90+
91+
echo "== passwordless sudo works (no su-root password fallback) =="
92+
sudo -n true || { echo "FAIL: passwordless sudo did not work"; exit 1; }
93+
94+
echo "== the exact apt-get escalation playwright runs succeeds =="
95+
sudo -n -- sh -c "apt-get --version >/dev/null" || { echo "FAIL: elevated apt-get"; exit 1; }
96+
97+
echo "== vp is installed and runnable =="
98+
vp --version
99+
100+
echo "ALL SMOKE CHECKS PASSED"
101+
'
102+
103+
# End-to-end proof for issue #2087: run a real Vitest browser-mode test
104+
# (Playwright, headless Chromium) with `vp test` inside the image. This
105+
# exercises the whole path the report needed and the smoke test only
106+
# simulates: `playwright install --with-deps` escalating to root via sudo,
107+
# then launching Chromium as the non-root vp user. On the old (sudo-less)
108+
# image the install step fails with `su: Authentication failure`.
109+
# Repro repo: https://github.com/why-reproductions-are-required/vite-plus-vitest-browser-mode
110+
- name: Vitest browser mode e2e (vp test)
111+
run: |
112+
docker run --rm -w /app "$IMAGE" bash -euc '
113+
git clone --depth 1 https://github.com/why-reproductions-are-required/vite-plus-vitest-browser-mode .
114+
vp install --frozen-lockfile
115+
vp exec playwright install --with-deps chromium
116+
vp test
117+
'

docker/Dockerfile

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ ARG VP_PR_VERSION=
2727

2828
# Toolchain image: include git (+ openssh-client for git+ssh dependencies) and a
2929
# C/C++ build toolchain so native addons (for example better-sqlite3 or sharp)
30-
# can compile during `vp install`.
30+
# can compile during `vp install`. `sudo` provides the non-root vp user an
31+
# escape hatch to root (see the sudoers entry below).
3132
RUN apt-get update \
3233
&& apt-get install -y --no-install-recommends \
3334
ca-certificates \
@@ -37,13 +38,26 @@ RUN apt-get update \
3738
build-essential \
3839
python3 \
3940
pkg-config \
41+
sudo \
4042
&& rm -rf /var/lib/apt/lists/* \
4143
&& useradd --create-home --shell /bin/bash vp \
4244
# Own /app for the vp user so downstream `WORKDIR /app` + `vp install` can write.
4345
&& mkdir -p /app \
44-
&& chown vp:vp /app
46+
&& chown vp:vp /app \
47+
# Grant the non-root vp user passwordless sudo. The image defaults to non-root
48+
# (bind-mounted files stay owned by uid 1000, and Chromium keeps its sandbox),
49+
# but build/CI/dev work still needs occasional root: installing extra apt
50+
# packages, and `playwright install --with-deps` for Vitest browser mode. When
51+
# not already root, Playwright runs the apt-get step via `sudo` if present and
52+
# otherwise falls back to `su root`, which fails here (the vp user has no
53+
# password). Providing NOPASSWD sudo lets that path succeed. This mirrors the
54+
# devcontainer base images (non-root user + NOPASSWD sudo).
55+
&& echo 'vp ALL=(root) NOPASSWD:ALL' > /etc/sudoers.d/vp \
56+
&& chmod 0440 /etc/sudoers.d/vp
4557

46-
# Run as a non-root user by default (mirrors oven/bun's `bun` and Deno's `deno`).
58+
# Run as a non-root user by default. Unlike a production runtime image, this is a
59+
# build/CI/dev image, so the vp user also has passwordless sudo (above) for the
60+
# root work those phases occasionally need.
4761
USER vp
4862

4963
ENV VP_HOME=/home/vp/.vite-plus \

docs/guide/docker.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ Tags track the `vp` version:
3030

3131
The examples use `:latest` to track the newest release; pin an exact tag or a
3232
digest if you need reproducible builds. The image is published for `linux/amd64`
33-
and `linux/arm64` and runs as a non-root user by default.
33+
and `linux/arm64` and runs as the non-root `vp` user by default. That user has
34+
passwordless `sudo`, so build/CI steps that need root (extra apt packages,
35+
`playwright install --with-deps`) work without changing the image user.
3436

3537
Browse all published versions and digests on the [GitHub package page](https://github.com/voidzero-dev/vite-plus/pkgs/container/vite-plus).
3638

@@ -136,6 +138,44 @@ build:
136138
137139
On GitHub Actions, prefer [`setup-vp`](./ci) instead of the image.
138140

141+
## Browser mode tests (Vitest / Playwright)
142+
143+
Running as the non-root `vp` user is what you want for browsers: Chromium keeps
144+
its sandbox (running a browser as root disables it). Install the browser and its
145+
system libraries in the job. `playwright install --with-deps` needs root to
146+
`apt-get install` those libraries. The `vp` user has passwordless `sudo`, so
147+
Playwright uses it to install them without changing the image user:
148+
149+
```yaml [.gitlab-ci.yml]
150+
test:
151+
image: ghcr.io/voidzero-dev/vite-plus:latest
152+
script:
153+
- vp install --frozen-lockfile
154+
- vp exec playwright install --with-deps chromium
155+
- vp test
156+
```
157+
158+
`vp exec` runs the project's own Playwright (from your lockfile), so it installs
159+
the browser revision your tests expect. Prefer it over `vpx playwright install`,
160+
which would download whatever Playwright is latest and can fetch a different
161+
browser revision.
162+
163+
To bake the browser and its libraries into a derived image instead of installing
164+
them on every run, install the project dependencies first so the baked browser
165+
matches your lockfile, then install with the project's Playwright (root is
166+
available through `sudo`):
167+
168+
```dockerfile [Dockerfile]
169+
FROM ghcr.io/voidzero-dev/vite-plus:latest
170+
WORKDIR /app
171+
COPY --chown=vp:vp package.json pnpm-lock.yaml pnpm-workspace.yaml .node-version* ./
172+
RUN vp install --frozen-lockfile
173+
RUN vp exec playwright install --with-deps chromium
174+
```
175+
176+
If Chromium crashes under load in CI, give the container more shared memory with
177+
`--ipc=host`; see the [Playwright Docker docs](https://playwright.dev/docs/docker).
178+
139179
## Devcontainers
140180

141181
Use the image as a ready-to-go development container with the toolchain
@@ -164,7 +204,11 @@ docker run --rm -it -v "$PWD:/app" -w /app ghcr.io/voidzero-dev/vite-plus vp bui
164204
those that use one have it available in every stage.
165205
- **Non-root user**: the image runs as the non-root `vp` user, so copy sources
166206
with `COPY --chown=vp:vp ...` as shown. Without it, `COPY` writes root-owned
167-
files that `vp install` cannot update (permission denied).
207+
files that `vp install` cannot update (permission denied). The `vp` user has
208+
passwordless `sudo` for the occasional root step (installing extra apt packages
209+
or `playwright install --with-deps`), so you rarely need to switch the image
210+
user. The production runtime stage is a separate, vp-free base image, so this
211+
convenience does not reach your deployed image.
168212
- **Native addons**: the image includes a C/C++ build toolchain (`build-essential`,
169213
`python3`), so native dependencies such as `better-sqlite3` compile during
170214
`vp install`.

0 commit comments

Comments
 (0)