Updated docker

This commit is contained in:
2026-09-03 10:40:55 +00:00
parent 09cbe61890
commit c121e0ca0a
5 changed files with 100 additions and 121 deletions
+29
View File
@@ -0,0 +1,29 @@
# dependencies — installed inside the image
node_modules
# build output — produced by the builder stage
dist
# version control & editor
.git
.gitignore
.vscode
.idea
# docker artifacts
Dockerfile
.dockerignore
# environment & local config
.env
.env.*
*.local
# caches & logs
.eslintcache
*.log
tmp
# repo tooling not needed inside the image
scripts
*.md
+45 -26
View File
@@ -1,34 +1,53 @@
# ── Mozimo — TanStack Start (Vite) ──────────────────────────
FROM node:lts-trixie-slim AS deps
# RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
else echo "Lockfile not found." && exit 1; \
fi
# Three-stage build:
# deps — reproducible dependency install (npm ci, cache-mounted)
# builder — production build (vite build → dist/)
# runner — lean runtime: production deps + dist + a plain Node server
# (no build tooling ships in the final image)
FROM node:lts-trixie-slim AS builder
ARG NODE_VERSION=24
# ── deps ────────────────────────────────────────────────────
FROM node:${NODE_VERSION}-trixie-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --no-audit --no-fund
# ── builder ─────────────────────────────────────────────────
FROM node:${NODE_VERSION}-trixie-slim AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
else echo "Lockfile not found." && exit 1; \
fi
RUN npm run build
FROM node:lts-trixie-slim AS runner
# ── runner ──────────────────────────────────────────────────
FROM node:${NODE_VERSION}-trixie-slim AS runner
LABEL org.opencontainers.image.title="Mozimo" \
org.opencontainers.image.description="Mozimo — luxury chocolate storefront (TanStack Start + Vite)"
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/vite.config.ts ./vite.config.ts
COPY --from=builder /app/tsconfig.json ./tsconfig.json
ENV NODE_ENV=production \
HOST=0.0.0.0 \
PORT=3000
# Install production dependencies only (react + @tanstack/* — no vite,
# tailwind, eslint or typescript ship in the runtime image).
# The cache mount never lands in image layers, so no cache cleaning needed.
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev --no-audit --no-fund
COPY --from=builder --chown=root:root /app/dist ./dist
COPY --chmod=0644 server.mjs ./
# Run as the unprivileged `node` user shipped with the base image.
USER node
EXPOSE 3000
CMD ["npm", "start"]
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/',{redirect:'manual'}).then(r=>process.exit(r.status<500?0:1)).catch(()=>process.exit(1))"
# Exec form: node runs as PID 1 and server.mjs handles SIGTERM gracefully.
CMD ["node", "server.mjs"]
+16 -1
View File
@@ -34,6 +34,21 @@ collection links point there. All product photography is self-hosted under
```bash
npm run dev # vite dev server (port 3000)
npm run build # production build to dist/
npm run start # serve the production build (vite preview)
npm start # serve the production build (node server.mjs)
npm run preview # serve the production build via vite preview
npm run lint # eslint
```
## Docker
```bash
docker build -t mozimo-web .
docker run -p 3000:3000 mozimo-web
```
Three-stage build (`deps → builder → runner`) on pinned `node:24-trixie-slim`:
dependencies install with `npm ci` and BuildKit cache mounts, the final image
runs as the unprivileged `node` user, ships only production dependencies plus
the build output and a small Node server (`server.mjs`: static assets with
immutable caching + gzip, SSR fetch-handler fallback, graceful shutdown), and
includes a `HEALTHCHECK`. `.dockerignore` keeps build context minimal.
+6 -87
View File
@@ -1,21 +1,17 @@
{
"name": "mozimo",
"version": "0.1.0",
"version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mozimo",
"version": "0.1.0",
"version": "0.2.0",
"dependencies": {
"@tanstack/react-router": "^1.168.3",
"@tanstack/react-start": "^1.167.5",
"@unpic/react": "^1.0.2",
"framer-motion": "^12.23.9",
"lucide-react": "^1.0.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"unpic": "^4.2.2"
"react-dom": "^19.1.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -32,7 +28,7 @@
"vite": "^8.0.2"
},
"engines": {
"node": ">=18.18.0"
"node": ">=24.0.0"
}
},
"node_modules/@babel/code-frame": {
@@ -3159,32 +3155,6 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@unpic/core": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@unpic/core/-/core-1.0.3.tgz",
"integrity": "sha512-aum9YNVUGso7MjGLD0Rp/08kywCGLqZ03/q6VQBFFakDBOXWEc8D4kPGcZ8v5wEnGRex3lE+++bOuucBp3KJ/w==",
"dependencies": {
"unpic": "^4.2.2"
}
},
"node_modules/@unpic/react": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@unpic/react/-/react-1.0.2.tgz",
"integrity": "sha512-5RmRfELwTF8w+4zjtQGqjpvX+RU2VLvis3xDCS1O2uWk0PZN2cvatL+3/KAR3mshAuRrkFGTX1XwyAezSXaoCA==",
"dependencies": {
"@unpic/core": "^1.0.3"
},
"peerDependencies": {
"next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"next": {
"optional": true
}
}
},
"node_modules/@vitejs/plugin-react": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz",
@@ -4123,32 +4093,6 @@
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/framer-motion": {
"version": "12.43.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz",
"integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==",
"dependencies": {
"motion-dom": "^12.43.0",
"motion-utils": "^12.39.0",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -4718,14 +4662,6 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "1.39.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.39.0.tgz",
"integrity": "sha512-y8nXoEwvqqIsF927NBWXODa4bfMrcUeEb/9sgpwFqg0gUjgn3j5Hznk+v7STmPgZ2iQ11JKlbQGdFRuTOwvYkA==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -4752,19 +4688,6 @@
"node": "*"
}
},
"node_modules/motion-dom": {
"version": "12.43.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz",
"integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==",
"dependencies": {
"motion-utils": "^12.39.0"
}
},
"node_modules/motion-utils": {
"version": "12.39.0",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
"integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5368,7 +5291,8 @@
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -5446,11 +5370,6 @@
"node": ">=4"
}
},
"node_modules/unpic": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/unpic/-/unpic-4.2.2.tgz",
"integrity": "sha512-z6T2ScMgRV2y2H8MwwhY5xHZWXhUx/YxtOCGJwfURSl7ypVy4HpLIMWoIZKnnxQa/RKzM0kg8hUh0paIrpLfvw=="
},
"node_modules/unplugin": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz",
+4 -7
View File
@@ -9,18 +9,15 @@
"scripts": {
"dev": "vite dev",
"build": "vite build",
"start": "vite preview --host 0.0.0.0",
"start": "node server.mjs",
"preview": "vite preview --host 0.0.0.0",
"lint": "eslint ."
},
"dependencies": {
"@tanstack/react-router": "^1.168.3",
"@tanstack/react-start": "^1.167.5",
"@unpic/react": "^1.0.2",
"framer-motion": "^12.23.9",
"lucide-react": "^1.0.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"unpic": "^4.2.2"
"react-dom": "^19.1.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
@@ -36,4 +33,4 @@
"typescript": "^6.0.2",
"vite": "^8.0.2"
}
}
}