Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c7b7cb0
docs(rfc): add official Docker image RFC
fengmk2 Jun 25, 2026
d5f7bda
docs(rfc): reflect install-script image build in Docker RFC
fengmk2 Jun 25, 2026
143347c
feat(docker): add official Vite+ toolchain image
fengmk2 Jun 25, 2026
45d9efd
ci(docker): build a preview image from pkg.pr.new
fengmk2 Jun 25, 2026
53e0aa7
style(docker): apply oxfmt formatting to Docker docs and RFC
fengmk2 Jun 25, 2026
bee12c5
refactor(docker): simplify image and speed up preview build
fengmk2 Jun 25, 2026
b39bcfb
docs(rfc): link the docs-example verification repo
fengmk2 Jun 25, 2026
0706d42
docs(docker): prune prod deps in a separate stage
fengmk2 Jun 25, 2026
05c6a7c
feat(docker): drop the baked default Node from the toolchain image
fengmk2 Jun 25, 2026
7e315a5
feat(docker): add Alpine (musl) toolchain image variant
fengmk2 Jun 25, 2026
b7159e2
docs(docker): note building a static SPA with the Alpine toolchain
fengmk2 Jun 25, 2026
dfef4fa
docs(docker): use 0.2.2 (first Docker release) in examples
fengmk2 Jun 25, 2026
7d4d5b9
docs(docker): COPY --chown=vp:vp in multi-stage examples
fengmk2 Jun 25, 2026
2a227ec
ci(docker): post a sticky PR comment with preview image tags
fengmk2 Jun 25, 2026
9883ff7
chore(docker): clarify comment-body form and add Dockerfile sync notes
fengmk2 Jun 25, 2026
3a59ef9
ci(docker): show preview image sizes in the sticky PR comment
fengmk2 Jun 25, 2026
bfb0436
docs(docker): use a text link for the GitHub package page
fengmk2 Jun 25, 2026
4786a7e
docs(docker): use :latest in examples instead of pinned versions
fengmk2 Jun 25, 2026
c66295d
docs(docker): don't require .node-version in the dependency COPY
fengmk2 Jun 26, 2026
ff20dd1
ci(docker): measure preview size via manifest inspect; drop redundant…
fengmk2 Jun 26, 2026
a67c9d9
docs(docker): optional .node-version glob; clarify latest vs pin
fengmk2 Jun 26, 2026
85b5692
feat(docker): add openssh-client and own /app for the vp user
fengmk2 Jun 26, 2026
36989b2
ci(release): make the Discord notification non-blocking
fengmk2 Jun 26, 2026
91b8f02
ci(release): notify Discord after Docker publish, include GHCR image
fengmk2 Jun 26, 2026
698d425
ci(release): drop redundant Release from discord-notify needs
fengmk2 Jun 26, 2026
7fb1189
docs(docker): use Node.js (not bare Node) per official naming
fengmk2 Jun 26, 2026
3030eee
docs(rfc): remove Status line from Docker image RFC
fengmk2 Jun 26, 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
154 changes: 154 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,157 @@ 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 }}
# Single-manifest image (no attestation index): simpler for consumers
# and lets the comment job read the size via `docker manifest inspect`.
provenance: false

# 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 }}

# Compressed download size from the registry manifest (config + layers),
# without pulling the images. Matches what `docker pull` transfers and what
# the GHCR package page shows.
- name: Measure image sizes
id: sizes
run: |
size() {
local bytes
bytes=$(docker manifest inspect "$1" | jq '[.config.size] + [.layers[].size] | add')
numfmt --to=si --suffix=B "$bytes"
}
{
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 | Compressed 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,
});
}
87 changes: 87 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,90 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release edit "v${VERSION}" --draft=false --repo "${{ github.repository }}"

# 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]
Comment thread
fengmk2 marked this conversation as resolved.
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

# Announce the release on Discord last, after the Docker images are published,
# so the message can include the GHCR image. Runs after the npm release
# (Release) and the images (publish-docker).
discord-notify:
name: Notify Discord
runs-on: ubuntu-latest
# publish-docker already needs Release, so depending on it orders this last.
needs: [check, publish-docker]
if: needs.check.outputs.version_changed == 'true'
env:
VERSION: ${{ needs.check.outputs.version }}
IMAGE: ghcr.io/voidzero-dev/vite-plus
steps:
# Non-blocking: a webhook failure must not mark the release run as failed
# (npm, GitHub release, and Docker images are already published).
- name: Send Discord notification
continue-on-error: true
uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 # v7.0.0
with:
webhook-url: ${{ secrets.DISCORD_RELEASES_WEBHOOK_URL }}
Expand All @@ -228,6 +311,10 @@ jobs:
• @voidzero-dev/vite-plus-core@${{ env.VERSION }}
• vite-plus@${{ env.VERSION }}

**Docker (GHCR):**
• `docker pull ${{ env.IMAGE }}:${{ env.VERSION }}`
• Alpine: `${{ env.IMAGE }}:${{ env.VERSION }}-alpine`

**Install:**
• macOS/Linux: `curl -fsSL https://vite.plus | bash`
• Windows: `irm https://vite.plus/ps1 | iex`
Expand Down
67 changes: 67 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 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 (+ openssh-client for git+ssh dependencies) 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 \
Comment thread
fengmk2 marked this conversation as resolved.
openssh-client \
build-essential \
python3 \
pkg-config \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --create-home --shell /bin/bash vp \
# Own /app for the vp user so downstream `WORKDIR /app` + `vp install` can write.
&& mkdir -p /app \
&& chown vp:vp /app

# 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.js 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
Comment thread
fengmk2 marked this conversation as resolved.
Comment thread
fengmk2 marked this conversation as resolved.
60 changes: 60 additions & 0 deletions docker/Dockerfile.alpine
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 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;
# openssh-client supports git+ssh dependencies.
RUN apk add --no-cache \
bash \
ca-certificates \
curl \
git \
openssh-client \
tar \
build-base \
python3 \
&& adduser -D -s /bin/bash vp \
# Own /app for the vp user so downstream `WORKDIR /app` + `vp install` can write.
&& mkdir -p /app \
&& chown vp:vp /app

# 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