Add Docker/Traefik deployment setup
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.git
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
|
design
|
||||||
|
*.md
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Used by docker-compose.yml for the Traefik Host() rule
|
||||||
|
DOMAIN=
|
||||||
|
|
||||||
|
GMAIL_USER=
|
||||||
|
GMAIL_APP_PASSWORD=
|
||||||
|
CONTACT_TO_EMAIL=
|
||||||
|
|
||||||
|
# Private pricing pages — JSON array, one object per page, reached at
|
||||||
|
# /<locale>/plans/<code>. `landing`/`webApp`/`care` take a number (rendered as
|
||||||
|
# €N, formatted per locale) or a string (used verbatim). Add or remove objects
|
||||||
|
# to add or remove pages. Generate an unguessable code with: openssl rand -hex 8
|
||||||
|
PLANS_PAGES=[{"code":"REPLACE_WITH_RANDOM_HEX","landing":500,"webApp":2000,"care":[25,60,120]}]
|
||||||
+2
-1
@@ -33,8 +33,9 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files
|
||||||
.env*
|
.env*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
FROM node:20-alpine AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
# The /plans/[code] route uses generateStaticParams + dynamicParams = false,
|
||||||
|
# so PLANS_PAGES must be known at build time — codes not baked in here will
|
||||||
|
# 404 at runtime no matter what PLANS_PAGES is set to when the container runs.
|
||||||
|
ARG PLANS_PAGES
|
||||||
|
ENV PLANS_PAGES=$PLANS_PAGES
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV HOSTNAME=0.0.0.0
|
||||||
|
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -23,14 +23,14 @@ export default function Footer({ locale, dict }: FooterProps) {
|
|||||||
[dict.footer.connect]: [
|
[dict.footer.connect]: [
|
||||||
{ label: "Source Code", href: "https://git.ksan.dev/ksan", external: true },
|
{ label: "Source Code", href: "https://git.ksan.dev/ksan", external: true },
|
||||||
{ label: "Email", href: "mailto:contact@ksan.dev", external: true },
|
{ label: "Email", href: "mailto:contact@ksan.dev", external: true },
|
||||||
{ label: "LinkedIn", href: "https://linkedin.com", external: true },
|
{ label: "LinkedIn", href: "https://www.linkedin.com/in/%C4%91or%C4%91e-k%C5%A1an-886871266", external: true },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const socials = [
|
const socials = [
|
||||||
{ icon: SiGitea, href: "https://git.ksan.dev/ksan", label: "Gitea" },
|
{ icon: SiGitea, href: "https://git.ksan.dev/ksan", label: "Gitea" },
|
||||||
{ icon: Mail, href: "mailto:contact@ksan.dev", label: "Email" },
|
{ icon: Mail, href: "mailto:contact@ksan.dev", label: "Email" },
|
||||||
{ icon: FaLinkedin, href: "https://linkedin.com", label: "LinkedIn" },
|
{ icon: FaLinkedin, href: "https://www.linkedin.com/in/%C4%91or%C4%91e-k%C5%A1an-886871266/", label: "LinkedIn" },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
services:
|
||||||
|
ksan-dev:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
PLANS_PAGES: ${PLANS_PAGES}
|
||||||
|
container_name: ksan-dev
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
networks:
|
||||||
|
- frontend
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.ksan-dev-http.rule=Host(`${DOMAIN}`) || Host(`www.${DOMAIN}`)
|
||||||
|
- traefik.http.routers.ksan-dev-http.entrypoints=web
|
||||||
|
- traefik.http.routers.ksan-dev-https.tls=true
|
||||||
|
- traefik.http.routers.ksan-dev-https.tls.certresolver=cloudflare
|
||||||
|
- traefik.http.routers.ksan-dev-https.entrypoints=websecure
|
||||||
|
- traefik.http.routers.ksan-dev-https.rule=Host(`${DOMAIN}`) || Host(`www.${DOMAIN}`)
|
||||||
|
- traefik.http.services.ksan-dev.loadbalancer.server.port=3000
|
||||||
|
|
||||||
|
networks:
|
||||||
|
frontend:
|
||||||
|
external: true
|
||||||
@@ -2,6 +2,7 @@ import type { NextConfig } from "next";
|
|||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
allowedDevOrigins: ["192.168.100.6"],
|
allowedDevOrigins: ["192.168.100.6"],
|
||||||
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
Reference in New Issue
Block a user