Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
35b55a6
docs(rfc): add official Docker image RFC
fengmk2 Jun 25, 2026
2760ea5
docs(rfc): reflect install-script image build in Docker RFC
fengmk2 Jun 25, 2026
ea17dbe
feat(docker): add official Vite+ toolchain image
fengmk2 Jun 25, 2026
1e6450b
ci(docker): build a preview image from pkg.pr.new
fengmk2 Jun 25, 2026
49084e3
style(docker): apply oxfmt formatting to Docker docs and RFC
fengmk2 Jun 25, 2026
adcdbf6
refactor(docker): simplify image and speed up preview build
fengmk2 Jun 25, 2026
1250709
docs(rfc): link the docs-example verification repo
fengmk2 Jun 25, 2026
cd94960
docs(docker): prune prod deps in a separate stage
fengmk2 Jun 25, 2026
f546bc9
feat(docker): drop the baked default Node from the toolchain image
fengmk2 Jun 25, 2026
ea9542e
feat(docker): add Alpine (musl) toolchain image variant
fengmk2 Jun 25, 2026
8e331a6
docs(docker): note building a static SPA with the Alpine toolchain
fengmk2 Jun 25, 2026
c49f123
docs(docker): use 0.2.2 (first Docker release) in examples
fengmk2 Jun 25, 2026
9d628e1
docs(docker): COPY --chown=vp:vp in multi-stage examples
fengmk2 Jun 25, 2026
8bc91aa
ci(docker): post a sticky PR comment with preview image tags
fengmk2 Jun 25, 2026
74b6dc9
chore(docker): clarify comment-body form and add Dockerfile sync notes
fengmk2 Jun 25, 2026
901e972
ci(docker): show preview image sizes in the sticky PR comment
fengmk2 Jun 25, 2026
5bfb671
docs(docker): use a text link for the GitHub package page
fengmk2 Jun 25, 2026
d36ba17
docs(docker): use :latest in examples instead of pinned versions
fengmk2 Jun 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions .github/workflows/publish-to-pkg.pr.new.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,161 @@ jobs:
'./packages/cli' \
'./packages/core' \
'./packages/prompts'

# Build and push a preview Docker image from the pkg.pr.new build so the image
# can be verified before a real release. Tagged `pr-<number>`; never `latest`.
# See docker/Dockerfile and docs/guide/docker.md.
publish-docker-preview:
if: >-
github.repository == 'voidzero-dev/vite-plus' &&
contains(github.event.pull_request.labels.*.name, 'pkg.pr.new')
name: Docker preview image (${{ matrix.variant }})
runs-on: ubuntu-latest
needs: publish
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- variant: debian
dockerfile: docker/Dockerfile
tag_suffix: ''
- variant: alpine
dockerfile: docker/Dockerfile.alpine
tag_suffix: '-alpine'
env:
IMAGE: ghcr.io/voidzero-dev/vite-plus
TAG: pr-${{ github.event.pull_request.number }}${{ matrix.tag_suffix }}
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Builds from the pkg.pr.new packages for this PR (VP_PR_VERSION). The
# platform packages must exist first, hence `needs: publish`.
# amd64-only: this throwaway preview avoids the slow arm64 QEMU leg; arm64
# is covered by the release build and the test-install-sh-arm64 job.
- name: Build and push preview image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: docker
file: ${{ matrix.dockerfile }}
platforms: linux/amd64
push: true
tags: ${{ env.IMAGE }}:${{ env.TAG }}
build-args: |
VP_PR_VERSION=${{ github.event.pull_request.number }}
provenance: false

- name: Summary
run: |
{
echo "### Docker preview image (${{ matrix.variant }})"
echo ""
echo '```bash'
echo "docker pull ${IMAGE}:${TAG}"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

# Post (or update) a single sticky PR comment with the preview image tags after
# both variants publish successfully. Re-runs reuse the same comment via the
# hidden marker instead of creating a new one.
comment-docker-preview:
if: >-
github.repository == 'voidzero-dev/vite-plus' &&
contains(github.event.pull_request.labels.*.name, 'pkg.pr.new')
name: Comment Docker preview
runs-on: ubuntu-latest
needs: publish-docker-preview
permissions:
contents: read
packages: read
pull-requests: write
env:
IMAGE: ghcr.io/voidzero-dev/vite-plus
PR_NUMBER: ${{ github.event.pull_request.number }}
steps:
- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Measure image sizes
id: sizes
run: |
size() {
docker pull -q "$1" >/dev/null
numfmt --to=si --suffix=B "$(docker image inspect "$1" --format '{{.Size}}')"
}
{
echo "debian=$(size "${IMAGE}:pr-${PR_NUMBER}")"
echo "alpine=$(size "${IMAGE}:pr-${PR_NUMBER}-alpine")"
} >> "$GITHUB_OUTPUT"

- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
DEBIAN_SIZE: ${{ steps.sizes.outputs.debian }}
ALPINE_SIZE: ${{ steps.sizes.outputs.alpine }}
with:
script: |
const image = process.env.IMAGE;
const pr = context.payload.pull_request.number;
const marker = '<!-- docker-preview -->';
// Built as a line array (not a template literal) so the fenced code
// blocks don't collide with the YAML block-scalar indentation.
const body = [
Comment thread
fengmk2 marked this conversation as resolved.
marker,
'## 🐳 Docker preview images',
'',
"Built from this PR's pkg.pr.new build:",
'',
'| Image | Size |',
'| --- | --- |',
`| \`${image}:pr-${pr}\` | ${process.env.DEBIAN_SIZE} |`,
`| \`${image}:pr-${pr}-alpine\` | ${process.env.ALPINE_SIZE} |`,
'',
'```bash',
`docker pull ${image}:pr-${pr}`,
`docker pull ${image}:pr-${pr}-alpine`,
'```',
'',
'Quick check:',
'',
'```bash',
`docker run --rm ${image}:pr-${pr} vp --version`,
'```',
'',
'See [docs/guide/docker.md](https://github.com/voidzero-dev/vite-plus/blob/main/docs/guide/docker.md) for usage.',
].join('\n');
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
});
const existing = comments.find((c) => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
body,
});
}
67 changes: 67 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,70 @@ jobs:
• macOS/Linux: `curl -fsSL https://vite.plus | bash`
• Windows: `irm https://vite.plus/ps1 | iex`
embed-url: https://github.com/${{ github.repository }}/releases/tag/v${{ env.VERSION }}

# Build and push the official toolchain Docker image to GHCR after the npm
# release is published (the image installs vp from npm, so the version must
# exist first). See docker/Dockerfile and docs/guide/docker.md.
publish-docker:
name: Publish Docker image (${{ matrix.variant }})
runs-on: ubuntu-latest
needs: [check, Release]
if: needs.check.outputs.version_changed == 'true'
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
# Debian (glibc) is the default: tags get no suffix.
- variant: debian
dockerfile: docker/Dockerfile
suffix: ''
# Alpine (musl): tags get the -alpine suffix.
- variant: alpine
dockerfile: docker/Dockerfile.alpine
suffix: '-alpine'
env:
VERSION: ${{ needs.check.outputs.version }}
IMAGE: ghcr.io/voidzero-dev/vite-plus
steps:
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2

- uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0

- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.IMAGE }}
flavor: |
latest=false
suffix=${{ matrix.suffix }}
tags: |
type=semver,pattern={{version}},value=${{ env.VERSION }}
type=semver,pattern={{major}}.{{minor}},value=${{ env.VERSION }}
type=semver,pattern={{major}},value=${{ env.VERSION }}
type=raw,value=latest

- name: Build and push
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: docker
file: ${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VP_VERSION=${{ env.VERSION }}
provenance: false
62 changes: 62 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# syntax=docker/dockerfile:1
#
# Official Vite+ toolchain image.
#
# Bundles the `vp` CLI for the build, CI, and development phases. This is NOT a
# production runtime image: it ships the full toolchain (vite, rolldown, vitest,
# oxlint, ...) and is meant for use as a build stage, CI image, or devcontainer.
#
# For production, use the documented multi-stage pattern (see docs/guide/docker.md)
# where this image builds the app and the exact Node.js resolved from
# `.node-version` is copied into a small, vp-free runtime stage.

FROM debian:bookworm-slim

LABEL org.opencontainers.image.source="https://github.com/voidzero-dev/vite-plus" \
org.opencontainers.image.description="Vite+ toolchain image (vp CLI) for build, CI, and development" \
org.opencontainers.image.licenses="MIT"

# Version of vp to install. Override at build time:
# docker build --build-arg VP_VERSION=1.4.2 .
ARG VP_VERSION=latest

# Optional: build a preview image from a pkg.pr.new build instead of npm.
# Set to a PR number or commit SHA; when set it overrides VP_VERSION.
# docker build --build-arg VP_PR_VERSION=1569 .
ARG VP_PR_VERSION=

# Toolchain image: include git and a C/C++ build toolchain so native addons
# (for example better-sqlite3 or sharp) can compile during `vp install`.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
build-essential \
python3 \
pkg-config \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --create-home --shell /bin/bash vp

# Run as a non-root user by default (mirrors oven/bun's `bun` and Deno's `deno`).
USER vp

ENV VP_HOME=/home/vp/.vite-plus \
PATH=/home/vp/.vite-plus/bin:$PATH

# Install the vp global CLI. The installer downloads the platform package from
# npm (or from pkg.pr.new when VP_PR_VERSION is set). Node.js itself is
# provisioned per-project by vp at build time, honoring `.node-version` /
# `engines.node` / `devEngines.runtime`.
#
# The installer pre-provisions a default Node.js (~190 MB). Drop it: each project
# downloads its own pinned Node at build time, so the default is dead weight in a
# builder image. The node/npm/npx shims remain and fetch the right version on
# first use.
#
# Keep this install line in sync with docker/Dockerfile.alpine.
RUN curl -fsSL https://vite.plus | VP_VERSION="${VP_VERSION}" VP_PR_VERSION="${VP_PR_VERSION}" bash \
&& vp --version \
&& rm -rf "$VP_HOME/js_runtime"

WORKDIR /app

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Create /app with ownership for the vp user

When downstream Dockerfiles follow the new guide and run WORKDIR /app plus RUN vp install as the default vp user, the inherited /app directory from this image is root-owned because Docker creates missing WORKDIR directories as root even after USER. COPY --chown=vp:vp ... fixes the files but not the directory itself, so vp install cannot create node_modules under /app; the Alpine image has the same pattern. Please create/chown /app before switching users or set the workdir somewhere already writable by vp.

Useful? React with 👍 / 👎.

55 changes: 55 additions & 0 deletions docker/Dockerfile.alpine
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# syntax=docker/dockerfile:1
#
# Alpine (musl) variant of the official Vite+ toolchain image.
#
# Smaller base than the Debian image, but note the tradeoffs:
# - Vite+ installs Node.js from the unofficial musl builds
# (unofficial-builds.nodejs.org), which are NOT PGP-signed.
# - Some native addons need musl prebuilds or source compilation.
# - A musl Node.js binary only runs on a musl base, so pair this builder with
# an Alpine runtime stage (see docs/guide/docker.md).
#
# Prefer the Debian image (ghcr.io/voidzero-dev/vite-plus) unless you
# specifically need Alpine/musl.

FROM alpine:3.21

LABEL org.opencontainers.image.source="https://github.com/voidzero-dev/vite-plus" \
org.opencontainers.image.description="Vite+ toolchain image (vp CLI, Alpine/musl) for build, CI, and development" \
org.opencontainers.image.licenses="MIT"

# Version of vp to install. Override at build time:
# docker build --build-arg VP_VERSION=1.4.2 -f docker/Dockerfile.alpine .
ARG VP_VERSION=latest

# Optional: build a preview image from a pkg.pr.new build instead of npm.
ARG VP_PR_VERSION=

# build-base + python3 let native addons compile from source on musl.
RUN apk add --no-cache \
bash \
ca-certificates \
curl \
git \
tar \
build-base \
python3 \
&& adduser -D -s /bin/bash vp

# Run as a non-root user by default (mirrors oven/bun's `bun` and Deno's `deno`).
USER vp

ENV VP_HOME=/home/vp/.vite-plus \
PATH=/home/vp/.vite-plus/bin:$PATH

# Install the vp global CLI (musl build), then drop the pre-provisioned default
# Node.js: each project provisions its own pinned Node at build time, so the
# default is dead weight. The node/npm/npx shims remain and fetch the right
# version on first use.
#
# Keep this install line in sync with docker/Dockerfile.
RUN curl -fsSL https://vite.plus | VP_VERSION="${VP_VERSION}" VP_PR_VERSION="${VP_PR_VERSION}" bash \
&& vp --version \
&& rm -rf "$VP_HOME/js_runtime"

WORKDIR /app
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const guideSidebar = [
items: [
{ text: 'IDE Integration', link: '/guide/ide-integration' },
{ text: 'CI', link: '/guide/ci' },
{ text: 'Docker', link: '/guide/docker' },
{ text: 'Commit Hooks', link: '/guide/commit-hooks' },
{ text: 'Monorepo Guide', link: '/guide/monorepo' },
{ text: 'Troubleshooting', link: '/guide/troubleshooting' },
Expand Down
Loading
Loading