mirror of
https://github.com/JOYCEQL/magic-resume.git
synced 2026-06-01 23:38:48 +02:00
feat: docker build process, and update ci/cd deployment branch to main
This commit is contained in:
@@ -3,7 +3,7 @@ name: Deploy to Cloudflare
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- refacor/tanstack-start
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -33,6 +33,7 @@ jobs:
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
+13
-23
@@ -4,42 +4,32 @@ FROM node:20-alpine AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN npm install -g corepack@latest && corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS deps
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --force
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
FROM deps AS builder
|
||||
COPY . .
|
||||
|
||||
ENV NEXT_STANDALONE=1
|
||||
RUN pnpm run build
|
||||
RUN pnpm run build && pnpm prune --prod
|
||||
|
||||
FROM base AS runner
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
RUN adduser --system --uid 1001 nodeapp
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/server.mjs ./server.mjs
|
||||
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
USER nodeapp
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
CMD ["node", "server.mjs"]
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"start": "node .output/server/index.mjs",
|
||||
"start": "node server.mjs",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
import { createServer } from "node:http";
|
||||
import { createReadStream, existsSync, statSync } from "node:fs";
|
||||
import { extname, normalize, resolve } from "node:path";
|
||||
import { Readable } from "node:stream";
|
||||
import serverEntry from "./dist/server/server.js";
|
||||
|
||||
const clientDir = resolve(process.cwd(), "dist/client");
|
||||
const port = Number(process.env.PORT || 3000);
|
||||
const host = process.env.HOSTNAME || "0.0.0.0";
|
||||
|
||||
const MIME_TYPES = {
|
||||
".css": "text/css; charset=utf-8",
|
||||
".gif": "image/gif",
|
||||
".html": "text/html; charset=utf-8",
|
||||
".ico": "image/x-icon",
|
||||
".jpeg": "image/jpeg",
|
||||
".jpg": "image/jpeg",
|
||||
".js": "text/javascript; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".map": "application/json; charset=utf-8",
|
||||
".png": "image/png",
|
||||
".svg": "image/svg+xml",
|
||||
".txt": "text/plain; charset=utf-8",
|
||||
".ttf": "font/ttf",
|
||||
".webp": "image/webp",
|
||||
".woff": "font/woff",
|
||||
".woff2": "font/woff2",
|
||||
".xml": "application/xml; charset=utf-8"
|
||||
};
|
||||
|
||||
function getContentType(filePath) {
|
||||
const extension = extname(filePath).toLowerCase();
|
||||
return MIME_TYPES[extension] || "application/octet-stream";
|
||||
}
|
||||
|
||||
function toHeaders(nodeHeaders) {
|
||||
const headers = new Headers();
|
||||
for (const [key, value] of Object.entries(nodeHeaders)) {
|
||||
if (typeof value === "undefined") continue;
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) headers.append(key, item);
|
||||
} else {
|
||||
headers.set(key, value);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
function resolveStaticFile(pathname) {
|
||||
const decoded = decodeURIComponent(pathname);
|
||||
const normalized = normalize(decoded).replace(/^[/\\]+/, "");
|
||||
const absolutePath = resolve(clientDir, normalized);
|
||||
if (!absolutePath.startsWith(clientDir)) return null;
|
||||
if (!existsSync(absolutePath)) return null;
|
||||
const stats = statSync(absolutePath);
|
||||
if (!stats.isFile()) return null;
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
function tryServeStatic(req, res, url) {
|
||||
if (!url.pathname || url.pathname.endsWith("/")) return false;
|
||||
const filePath = resolveStaticFile(url.pathname);
|
||||
if (!filePath) return false;
|
||||
|
||||
res.statusCode = 200;
|
||||
res.setHeader("Content-Type", getContentType(filePath));
|
||||
if (url.pathname.startsWith("/assets/")) {
|
||||
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
||||
} else {
|
||||
res.setHeader("Cache-Control", "public, max-age=3600");
|
||||
}
|
||||
|
||||
if (req.method === "HEAD") {
|
||||
res.end();
|
||||
return true;
|
||||
}
|
||||
|
||||
createReadStream(filePath).pipe(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
function appendSetCookie(res, value) {
|
||||
const existing = res.getHeader("set-cookie");
|
||||
if (!existing) {
|
||||
res.setHeader("set-cookie", value);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(existing)) {
|
||||
res.setHeader("set-cookie", [...existing, value]);
|
||||
return;
|
||||
}
|
||||
res.setHeader("set-cookie", [String(existing), value]);
|
||||
}
|
||||
|
||||
createServer(async (req, res) => {
|
||||
try {
|
||||
const hostHeader = req.headers.host || `localhost:${port}`;
|
||||
const protocol = (req.headers["x-forwarded-proto"] || "http").toString().split(",")[0].trim();
|
||||
const url = new URL(req.url || "/", `${protocol}://${hostHeader}`);
|
||||
|
||||
if (tryServeStatic(req, res, url)) return;
|
||||
|
||||
const method = (req.method || "GET").toUpperCase();
|
||||
const hasBody = method !== "GET" && method !== "HEAD";
|
||||
const init = {
|
||||
method,
|
||||
headers: toHeaders(req.headers)
|
||||
};
|
||||
|
||||
if (hasBody) {
|
||||
init.body = Readable.toWeb(req);
|
||||
init.duplex = "half";
|
||||
}
|
||||
|
||||
const request = new Request(url, init);
|
||||
const response = await serverEntry.fetch(request);
|
||||
|
||||
res.statusCode = response.status;
|
||||
response.headers.forEach((value, key) => {
|
||||
if (key.toLowerCase() === "set-cookie") {
|
||||
appendSetCookie(res, value);
|
||||
} else {
|
||||
res.setHeader(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
if (method === "HEAD" || !response.body) {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
Readable.fromWeb(response.body).pipe(res);
|
||||
} catch (error) {
|
||||
console.error("Server error:", error);
|
||||
if (!res.headersSent) {
|
||||
res.statusCode = 500;
|
||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
}
|
||||
res.end("Internal Server Error");
|
||||
}
|
||||
}).listen(port, host, () => {
|
||||
console.log(`Server running at http://${host}:${port}`);
|
||||
});
|
||||
Reference in New Issue
Block a user