diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b078083 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +node_modules +dist +.astro +.git +.github +.vscode +.claude +Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8ba6a14 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:26-slim AS build +RUN npm install --global bun +WORKDIR /app +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile +COPY . . +ARG PR_PREVIEW="" +ENV PR_PREVIEW=$PR_PREVIEW +RUN bun run build + +FROM nginx:1.29-alpine +COPY infra/nginx.conf /etc/nginx/nginx.conf +COPY --from=build /app/dist /usr/share/nginx/html +USER nobody +CMD ["nginx", "-g", "daemon off;"] diff --git a/Makefile b/Makefile index 53885ac..d110802 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,11 @@ dev: ## Start development server preview: ## Build and preview production locally bun run preview +.PHONY: pr-preview +pr-preview: ## Build and serve the site as a PR preview locally + PR_PREVIEW=local bun run build + bun run preview + ## Build .PHONY: build diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..dde09f1 --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: nginx -g 'daemon off;' +release: echo 'deployed' diff --git a/astro.config.ts b/astro.config.ts index 872e788..49929b3 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -8,6 +8,7 @@ import remarkPythonRefs from "./src/plugins/remark-python-refs"; const isDev = process.env.NODE_ENV === "development" || process.argv.includes("dev"); + // Load redirects generated by the migration script let redirects: Record = {}; try { diff --git a/infra/nginx.conf b/infra/nginx.conf new file mode 100644 index 0000000..a1a7a12 --- /dev/null +++ b/infra/nginx.conf @@ -0,0 +1,38 @@ +pid /tmp/nginx.pid; +error_log /dev/stderr warn; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /dev/stdout; + + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + gzip on; + gzip_types text/css application/javascript application/json image/svg+xml text/plain application/xml; + + server { + listen unix:/var/run/cabotage/cabotage.sock; + root /usr/share/nginx/html; + index index.html; + + location = /_health/ { + add_header Content-Type text/plain; + return 200 'OK'; + } + + error_page 404 /404.html; + + location / { + try_files $uri $uri/ =404; + } + } +} diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro index 3b12c8b..1793d9d 100644 --- a/src/components/BaseHead.astro +++ b/src/components/BaseHead.astro @@ -6,6 +6,7 @@ interface Props { } import { withBase, stripDescriptionLinks } from "../lib/utils"; +const isPrPreview = Boolean(process.env.PR_PREVIEW); const { title, description: rawDescription = "The official blog of the Python core development team.", image } = Astro.props; const description = stripDescriptionLinks(rawDescription); const baseUrl = import.meta.env.DEV ? Astro.url.origin : Astro.site; @@ -38,6 +39,7 @@ const ogImage = image ? new URL(image, baseUrl) : new URL(withBase("/og-default. {title} +{isPrPreview && } @@ -56,5 +58,11 @@ const ogImage = image ? new URL(image, baseUrl) : new URL(withBase("/og-default. - - +{ + !isPrPreview && ( + <> +