Updated docker
This commit is contained in:
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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.
|
||||
|
||||
Generated
+6
-87
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user