aspire icon indicating copy to clipboard operation
aspire copied to clipboard

Container image for JavaScript apps are huge

Open davidfowl opened this issue 2 months ago • 9 comments

774 MB for a static site is wild 😄, we should pick a different base image here.

This was the multi image build (amd64, arm64):

Image

This is the amd64 only image:

Image

cc @eerhardt

davidfowl avatar Nov 01 '25 18:11 davidfowl

OK with some AI help I got to this:

Image

Here's the docker file I dropped in:

# ---------- build: produce /site ----------
FROM node:22-alpine AS build
WORKDIR /src

# Deterministic installs; pick the lock you actually use
RUN corepack enable
COPY package.json pnpm-lock.yaml* package-lock.json* yarn.lock* ./

# Use BuildKit caches (fast in CI and local)
RUN --mount=type=cache,target=/root/.local/share/pnpm \
    --mount=type=cache,target=/root/.npm \
    --mount=type=cache,target=/usr/local/share/.cache/yarn \
    sh -lc '\
      if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; \
      elif [ -f package-lock.json ]; then npm ci; \
      elif [ -f yarn.lock ]; then yarn install --frozen-lockfile; \
      else npm install; fi'

# Copy source and build to /site (see vite.config.ts below)
COPY . .
ENV NODE_ENV=production
RUN sh -lc '\
      if [ -f pnpm-lock.yaml ]; then pnpm run build; \
      elif [ -f package-lock.json ]; then npm run build; \
      elif [ -f yarn.lock ]; then yarn build; \
      else npm run build; fi'

# ---------- artifact image: just the files ----------
FROM scratch AS static-artifact
LABEL org.opencontainers.image.title="vite-static-artifact"
COPY --from=build /src/dist/ /app/dist

It ends up with a from scrath container that just has the static assets

davidfowl avatar Nov 01 '25 18:11 davidfowl

Any reason we aren’t using the current LTS version (24-alpine)?

We should also consider whether we need to copy .npmrc (or similar files) for private registry support when building in a container.

danegsta avatar Nov 02 '25 17:11 danegsta

The current Dockerfile copies everything 😬

davidfowl avatar Nov 02 '25 17:11 davidfowl

The current Dockerfile copies everything 😬

There’s two problems to solve for in a Dockerfile; minimizing build time on subsequent builds and minimizing the final image size. Copying the built files to a scratch stage solves for the image size, while copying only the package and lock files before installing is intended to help with the rebuild time.

The idea is that every command in a Dockerfile gets a cache layer generated (but the cache is only used if all previous commands matched a cached layer; if any commands don’t result in a cache hit, all subsequent commands are run from scratch). RUN commands are cached based on the command itself, while for COPY commands the cache is based on the contents of the copied files; if there’s any changes on a subsequent copy, that cache layer (and therefore any subsequent ones) is invalidated. You don’t want to copy everything before installing because that increases the chance of a cache miss due to code changes even though the actual package dependencies haven’t changed.

Obviously that gets complicated when you need additional files for auth, config, etc.

danegsta avatar Nov 02 '25 18:11 danegsta

It ends up with a from scrath container that just has the static assets

What's the point of this part? Are we planning on pushing the docker image anywhere?

My understanding is that we were just planning on using it as a way to build and then copy out the files somewhere else. Why does it matter if the files are in a separate image that doesn't contain anything else?

eerhardt avatar Nov 03 '25 16:11 eerhardt

Main argument for keeping a smaller final image is to avoid ballooning the image store more than necessary.

danegsta avatar Nov 04 '25 00:11 danegsta

If you aren't pushing the final image to another store, wouldn't copying the final assets into ANOTHER image "balloon the image store" more than necessary?

eerhardt avatar Nov 04 '25 16:11 eerhardt

It depends on whether the image is tagged or not; if it's an intermediate step, it may be added to the image cache, but you won't get a tagged image. That means docker image prune can clean it up as an untagged, unreferenced image. If the full file set is included in the final tagged image, the user has to delete it manually.

danegsta avatar Nov 04 '25 17:11 danegsta

@eerhardt You fixed this yea?

davidfowl avatar Nov 16 '25 08:11 davidfowl

No. Not yet

eerhardt avatar Nov 18 '25 17:11 eerhardt