Skip to content

Commit 09b8da4

Browse files
committed
Add static container setup and GHCR pipeline
1 parent 7a6b134 commit 09b8da4

File tree

7 files changed

+182
-1
lines changed

7 files changed

+182
-1
lines changed

.dockerignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
node_modules
2+
.next
3+
out
4+
dist
5+
npm-debug.log
6+
.npmrc
7+
.git
8+
.gitignore
9+
Dockerfile
10+
.dockerignore
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: Build and Publish Container Image
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ["v*"]
7+
pull_request:
8+
workflow_dispatch:
9+
10+
env:
11+
REGISTRY: ghcr.io
12+
IMAGE_NAME: "www-website"
13+
14+
jobs:
15+
build:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
packages: write
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Compute image name (lowercase owner/repo)
26+
id: img
27+
run: |
28+
OWNER="$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')"
29+
REPO_NAME="${GITHUB_REPOSITORY##*/}"
30+
IMAGE_NAME="${IMAGE_NAME:-$REPO_NAME}"
31+
IMAGE_NAME="$(echo "${IMAGE_NAME}" | tr '[:upper:]' '[:lower:]')"
32+
echo "IMAGE=${REGISTRY}/${OWNER}/${IMAGE_NAME}" >> "$GITHUB_ENV"
33+
34+
- name: Docker metadata
35+
id: meta
36+
uses: docker/metadata-action@v5
37+
with:
38+
images: ${{ env.IMAGE }}
39+
tags: |
40+
type=sha
41+
type=ref,event=tag
42+
type=ref,event=branch,enable=${{ github.ref == 'refs/heads/main' }}
43+
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
44+
45+
- name: Set up Buildx
46+
uses: docker/setup-buildx-action@v3
47+
48+
- name: Build image for tests
49+
uses: docker/build-push-action@v6
50+
with:
51+
context: .
52+
file: ./Dockerfile
53+
push: false
54+
load: true
55+
tags: ${{ env.IMAGE }}:ci
56+
cache-from: type=gha
57+
cache-to: type=gha,mode=max
58+
59+
- name: Run smoke test (curl /)
60+
env:
61+
IMAGE_UNDER_TEST: ${{ env.IMAGE }}:ci
62+
run: |
63+
set -euo pipefail
64+
cid=$(docker run -d -p 0:3000 "$IMAGE_UNDER_TEST")
65+
trap "docker rm -f $cid >/dev/null 2>&1" EXIT
66+
port=$(docker port "$cid" 3000/tcp | sed -E 's/.*:([0-9]+)$/\1/')
67+
for i in {1..20}; do
68+
if curl -fsS "http://127.0.0.1:${port}/" > /dev/null; then
69+
exit 0
70+
fi
71+
sleep 1
72+
done
73+
echo "Service did not respond on / after 20s" >&2
74+
exit 1
75+
76+
- name: Log in to GHCR
77+
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/main') || startsWith(github.ref, 'refs/tags/'))
78+
uses: docker/login-action@v3
79+
with:
80+
registry: ${{ env.REGISTRY }}
81+
username: ${{ github.actor }}
82+
# Prefer CR_PAT if provided (for cross-repo scopes); fallback to GITHUB_TOKEN
83+
password: ${{ secrets.CR_PAT != '' && secrets.CR_PAT || secrets.GITHUB_TOKEN }}
84+
85+
- name: Build and push
86+
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/main') || startsWith(github.ref, 'refs/tags/'))
87+
uses: docker/build-push-action@v6
88+
with:
89+
context: .
90+
file: ./Dockerfile
91+
push: true
92+
tags: ${{ steps.meta.outputs.tags }}
93+
labels: ${{ steps.meta.outputs.labels }}
94+
cache-from: type=gha
95+
cache-to: type=gha,mode=max

Caddyfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
:3000 {
2+
respond /healthz 200
3+
handle_path /blog* {
4+
reverse_proxy blog:3000
5+
}
6+
handle {
7+
reverse_proxy site:3000
8+
}
9+
}

Dockerfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# syntax=docker/dockerfile:1
2+
3+
FROM node:22-alpine AS build
4+
WORKDIR /app
5+
6+
ENV NODE_ENV=production \
7+
NEXT_TELEMETRY_DISABLED=1
8+
9+
RUN apk add --no-cache libc6-compat
10+
11+
COPY package.json package-lock.json ./
12+
RUN npm ci
13+
14+
COPY . .
15+
RUN npm run build
16+
17+
FROM caddy:2.9-alpine AS runner
18+
WORKDIR /srv
19+
20+
RUN addgroup -S caddy && adduser -S caddy -G caddy
21+
22+
COPY --from=build --chown=caddy:caddy /app/out ./
23+
24+
EXPOSE 3000
25+
USER caddy
26+
ENTRYPOINT ["caddy", "file-server", "--root=/srv", "--listen=:3000"]

app/opengraph-image.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { ImageResponse } from "next/og";
44
import { readFile } from "fs/promises";
55
import { join } from "path";
66

7+
export const dynamic = "force-static";
8+
79
export const alt = "About DevSH";
810
export const size = {
911
width: 256,
@@ -36,4 +38,4 @@ export default async function OgImage() {
3638
...size
3739
}
3840
)
39-
}
41+
}

docker-compose.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
version: "3.9"
2+
3+
services:
4+
site:
5+
build: .
6+
image: devsh-website
7+
read_only: true
8+
tmpfs:
9+
- /tmp
10+
- /config
11+
- /data
12+
restart: unless-stopped
13+
14+
blog:
15+
build:
16+
context: ../blog
17+
dockerfile: Dockerfile
18+
args:
19+
BLOG_BASEURL: /blog/
20+
image: devsh-blog
21+
read_only: true
22+
tmpfs:
23+
- /tmp
24+
- /config
25+
- /data
26+
restart: unless-stopped
27+
28+
proxy:
29+
image: caddy:2.9-alpine
30+
depends_on:
31+
- site
32+
- blog
33+
ports:
34+
- "3000:3000"
35+
volumes:
36+
- ./Caddyfile:/etc/caddy/Caddyfile:ro
37+
restart: unless-stopped

next.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
4+
output: 'export',
45
pageExtensions: ['ts', 'tsx', 'md', 'mdx'],
56
images: {
7+
unoptimized: true,
68
remotePatterns: [
79
{
810
protocol: 'https',

0 commit comments

Comments
 (0)