build: optimize Dockerfiles for production and pin dependencies
Implement multi-stage build and static server for frontend Run containers as non-root user and set production environment Pin all package dependencies to exact versions
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY backend/package.json ./backend/package.json
|
||||
COPY frontend/package.json ./frontend/package.json
|
||||
RUN corepack enable && corepack prepare pnpm@10.33.2 --activate && pnpm install --frozen-lockfile --filter @pokopia/backend...
|
||||
COPY backend ./backend
|
||||
COPY data ./data
|
||||
COPY system-wordings.ts ./system-wordings.ts
|
||||
RUN mkdir -p /app/uploads && chown -R node:node /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app/backend
|
||||
COPY backend/package.json ./
|
||||
RUN corepack enable && pnpm install
|
||||
COPY backend/. .
|
||||
COPY data /app/data
|
||||
COPY package.json /app/package.json
|
||||
COPY system-wordings.ts /app/system-wordings.ts
|
||||
USER node
|
||||
EXPOSE 3001
|
||||
CMD ["pnpm", "run", "start"]
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
"test": "node --test --import tsx tests/*.test.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "latest",
|
||||
"@fastify/multipart": "^10.0.0",
|
||||
"@fastify/rate-limit": "^10.3.0",
|
||||
"@fastify/static": "^9.1.3",
|
||||
"fastify": "latest",
|
||||
"pg": "latest"
|
||||
"@fastify/cors": "11.2.0",
|
||||
"@fastify/multipart": "10.0.0",
|
||||
"@fastify/rate-limit": "10.3.0",
|
||||
"@fastify/static": "9.1.3",
|
||||
"fastify": "5.8.5",
|
||||
"pg": "8.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "latest",
|
||||
"@types/pg": "latest",
|
||||
"tsx": "latest",
|
||||
"typescript": "latest"
|
||||
"@types/node": "25.6.0",
|
||||
"@types/pg": "8.20.0",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,11 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: frontend/Dockerfile
|
||||
args:
|
||||
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://localhost:3001}
|
||||
VITE_SITE_URL: ${VITE_SITE_URL:-https://pokopiawiki.tootaio.com}
|
||||
environment:
|
||||
VITE_API_BASE_URL: http://localhost:3001
|
||||
VITE_SITE_URL: https://pokopiawiki.tootaio.com
|
||||
PORT: 20015
|
||||
ports:
|
||||
- "20015:20015"
|
||||
depends_on:
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
FROM node:22-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY backend/package.json ./backend/package.json
|
||||
COPY frontend/package.json ./frontend/package.json
|
||||
RUN corepack enable && corepack prepare pnpm@10.33.2 --activate && pnpm install --frozen-lockfile --filter @pokopia/frontend...
|
||||
COPY frontend ./frontend
|
||||
COPY system-wordings.ts ./system-wordings.ts
|
||||
|
||||
ARG VITE_API_BASE_URL=http://localhost:3001
|
||||
ARG VITE_SITE_URL=https://pokopiawiki.tootaio.com
|
||||
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
|
||||
ENV VITE_SITE_URL=$VITE_SITE_URL
|
||||
RUN pnpm --filter @pokopia/frontend build
|
||||
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app/frontend
|
||||
COPY frontend/package.json ./
|
||||
RUN corepack enable && pnpm install
|
||||
COPY frontend/. .
|
||||
COPY package.json /app/package.json
|
||||
COPY system-wordings.ts /app/system-wordings.ts
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=20015
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/frontend/dist ./dist
|
||||
COPY frontend/static-server.mjs ./static-server.mjs
|
||||
|
||||
USER node
|
||||
EXPOSE 20015
|
||||
CMD ["pnpm", "run", "dev"]
|
||||
CMD ["node", "static-server.mjs"]
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@vitejs/plugin-vue": "latest",
|
||||
"vite": "latest",
|
||||
"vue": "latest",
|
||||
"vue-i18n": "^11.4.0",
|
||||
"vue-router": "latest"
|
||||
"@iconify/vue": "5.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.6",
|
||||
"vite": "8.0.10",
|
||||
"vue": "3.5.33",
|
||||
"vue-i18n": "11.4.0",
|
||||
"vue-router": "5.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "latest",
|
||||
"@vue/tsconfig": "latest",
|
||||
"typescript": "latest",
|
||||
"vitest": "latest",
|
||||
"vue-tsc": "latest"
|
||||
"@types/node": "25.6.0",
|
||||
"@vue/tsconfig": "0.9.1",
|
||||
"typescript": "6.0.3",
|
||||
"vitest": "4.1.5",
|
||||
"vue-tsc": "3.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
82
frontend/static-server.mjs
Normal file
82
frontend/static-server.mjs
Normal file
@@ -0,0 +1,82 @@
|
||||
import { createReadStream } from 'node:fs';
|
||||
import { stat } from 'node:fs/promises';
|
||||
import { createServer } from 'node:http';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), 'dist');
|
||||
const indexPath = path.join(root, 'index.html');
|
||||
const host = process.env.HOST ?? '0.0.0.0';
|
||||
const port = Number.parseInt(process.env.PORT ?? '20015', 10);
|
||||
|
||||
const contentTypes = new Map([
|
||||
['.css', 'text/css; charset=utf-8'],
|
||||
['.gif', 'image/gif'],
|
||||
['.html', 'text/html; charset=utf-8'],
|
||||
['.ico', 'image/x-icon'],
|
||||
['.jpg', 'image/jpeg'],
|
||||
['.js', 'text/javascript; charset=utf-8'],
|
||||
['.json', 'application/json; charset=utf-8'],
|
||||
['.png', 'image/png'],
|
||||
['.svg', 'image/svg+xml'],
|
||||
['.txt', 'text/plain; charset=utf-8'],
|
||||
['.wasm', 'application/wasm'],
|
||||
['.webmanifest', 'application/manifest+json; charset=utf-8'],
|
||||
['.webp', 'image/webp'],
|
||||
['.woff', 'font/woff'],
|
||||
['.woff2', 'font/woff2'],
|
||||
['.xml', 'application/xml; charset=utf-8']
|
||||
]);
|
||||
|
||||
function isInsideRoot(filePath) {
|
||||
const relativePath = path.relative(root, filePath);
|
||||
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
||||
}
|
||||
|
||||
function resolvePath(url) {
|
||||
try {
|
||||
const pathname = new URL(url ?? '/', 'http://localhost').pathname;
|
||||
const filePath = path.resolve(root, `.${decodeURIComponent(pathname)}`);
|
||||
return isInsideRoot(filePath) ? filePath : indexPath;
|
||||
} catch {
|
||||
return indexPath;
|
||||
}
|
||||
}
|
||||
|
||||
async function findStaticFile(url) {
|
||||
const filePath = resolvePath(url);
|
||||
try {
|
||||
const fileStat = await stat(filePath);
|
||||
if (fileStat.isFile()) {
|
||||
return { filePath, fileStat };
|
||||
}
|
||||
} catch {
|
||||
return { filePath: indexPath, fileStat: await stat(indexPath) };
|
||||
}
|
||||
|
||||
return { filePath: indexPath, fileStat: await stat(indexPath) };
|
||||
}
|
||||
|
||||
createServer(async (request, response) => {
|
||||
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
||||
response.writeHead(405);
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const { filePath, fileStat } = await findStaticFile(request.url);
|
||||
const contentType = contentTypes.get(path.extname(filePath)) ?? 'application/octet-stream';
|
||||
|
||||
response.writeHead(200, {
|
||||
'Cache-Control': filePath.endsWith('index.html') ? 'no-cache' : 'public, max-age=31536000, immutable',
|
||||
'Content-Length': fileStat.size,
|
||||
'Content-Type': contentType
|
||||
});
|
||||
|
||||
if (request.method === 'HEAD') {
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
createReadStream(filePath).pipe(response);
|
||||
}).listen(port, host);
|
||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@@ -11,72 +11,72 @@ importers:
|
||||
backend:
|
||||
dependencies:
|
||||
'@fastify/cors':
|
||||
specifier: latest
|
||||
specifier: 11.2.0
|
||||
version: 11.2.0
|
||||
'@fastify/multipart':
|
||||
specifier: ^10.0.0
|
||||
specifier: 10.0.0
|
||||
version: 10.0.0
|
||||
'@fastify/rate-limit':
|
||||
specifier: ^10.3.0
|
||||
specifier: 10.3.0
|
||||
version: 10.3.0
|
||||
'@fastify/static':
|
||||
specifier: ^9.1.3
|
||||
specifier: 9.1.3
|
||||
version: 9.1.3
|
||||
fastify:
|
||||
specifier: latest
|
||||
specifier: 5.8.5
|
||||
version: 5.8.5
|
||||
pg:
|
||||
specifier: latest
|
||||
specifier: 8.20.0
|
||||
version: 8.20.0
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: latest
|
||||
specifier: 25.6.0
|
||||
version: 25.6.0
|
||||
'@types/pg':
|
||||
specifier: latest
|
||||
specifier: 8.20.0
|
||||
version: 8.20.0
|
||||
tsx:
|
||||
specifier: latest
|
||||
specifier: 4.21.0
|
||||
version: 4.21.0
|
||||
typescript:
|
||||
specifier: latest
|
||||
specifier: 6.0.3
|
||||
version: 6.0.3
|
||||
|
||||
frontend:
|
||||
dependencies:
|
||||
'@iconify/vue':
|
||||
specifier: ^5.0.0
|
||||
specifier: 5.0.0
|
||||
version: 5.0.0(vue@3.5.33(typescript@6.0.3))
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: latest
|
||||
specifier: 6.0.6
|
||||
version: 6.0.6(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.33(typescript@6.0.3))
|
||||
vite:
|
||||
specifier: latest
|
||||
specifier: 8.0.10
|
||||
version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vue:
|
||||
specifier: latest
|
||||
specifier: 3.5.33
|
||||
version: 3.5.33(typescript@6.0.3)
|
||||
vue-i18n:
|
||||
specifier: ^11.4.0
|
||||
specifier: 11.4.0
|
||||
version: 11.4.0(vue@3.5.33(typescript@6.0.3))
|
||||
vue-router:
|
||||
specifier: latest
|
||||
specifier: 5.0.6
|
||||
version: 5.0.6(@vue/compiler-sfc@3.5.33)(vue@3.5.33(typescript@6.0.3))
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: latest
|
||||
specifier: 25.6.0
|
||||
version: 25.6.0
|
||||
'@vue/tsconfig':
|
||||
specifier: latest
|
||||
specifier: 0.9.1
|
||||
version: 0.9.1(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3))
|
||||
typescript:
|
||||
specifier: latest
|
||||
specifier: 6.0.3
|
||||
version: 6.0.3
|
||||
vitest:
|
||||
specifier: latest
|
||||
specifier: 4.1.5
|
||||
version: 4.1.5(@types/node@25.6.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vue-tsc:
|
||||
specifier: latest
|
||||
specifier: 3.2.7
|
||||
version: 3.2.7(typescript@6.0.3)
|
||||
|
||||
packages:
|
||||
|
||||
Reference in New Issue
Block a user