From 3438f8d4058769857afcaa711133d6d67ec235c1 Mon Sep 17 00:00:00 2001 From: Aleksander Cynarski Date: Sun, 15 Mar 2026 23:49:15 +0100 Subject: [PATCH] add Docker and CI/CD pipeline with ledo Configure Next.js static export with nginx serving, Dockerfile (multi-stage build), docker-compose for prod/dev, Gitea Actions pipelines for build-on-push and tag-based releases using ledo tool. Co-Authored-By: Claude Opus 4.6 (1M context) --- .dockerignore | 9 +++++++ .gitea/workflows/docker-build.yml | 27 +++++++++++++++++++++ .gitea/workflows/docker-tag.yml | 35 +++++++++++++++++++++++++++ .ledo.yml | 11 +++++++++ Dockerfile | 35 +++++++++++++++++++++++++++ docker/docker-compose.dev.yml | 17 ++++++++++++++ docker/docker-compose.yml | 11 +++++++++ docker/docker-entrypoint.sh | 39 +++++++++++++++++++++++++++++++ docker/nginx.conf | 29 +++++++++++++++++++++++ docker/test-entrypoint.sh | 38 ++++++++++++++++++++++++++++++ next.config.ts | 2 +- 11 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/docker-build.yml create mode 100644 .gitea/workflows/docker-tag.yml create mode 100644 .ledo.yml create mode 100644 Dockerfile create mode 100644 docker/docker-compose.dev.yml create mode 100644 docker/docker-compose.yml create mode 100755 docker/docker-entrypoint.sh create mode 100644 docker/nginx.conf create mode 100755 docker/test-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..93f6b16 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +.next +.git +.gitea +.automancer +docker +*.md +.env* +.DS_Store diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml new file mode 100644 index 0000000..30a76a2 --- /dev/null +++ b/.gitea/workflows/docker-build.yml @@ -0,0 +1,27 @@ +name: Docker - build image +run-name: ${{ gitea.actor }} builds image for project 🏗️ +on: + push: + branches: + - "main" +jobs: + builder: + name: 🏗️ Docker builder (latest from master) 🏗️ + runs-on: dind + steps: + - name: "[docker] registry login" + uses: docker/login-action@v2 + with: + registry: git.cynarski.pl + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + - name: "[code] repository checkout" + uses: actions/checkout@v3 + with: + submodules: recursive + - name: "[ledo] Image build" + run: ledo image build + - name: "[ledo] Image push" + run: ledo image push + - name: "[docker] system prune" + run: docker system prune -af --volumes diff --git a/.gitea/workflows/docker-tag.yml b/.gitea/workflows/docker-tag.yml new file mode 100644 index 0000000..445b050 --- /dev/null +++ b/.gitea/workflows/docker-tag.yml @@ -0,0 +1,35 @@ +name: Docker - build image +run-name: ${{ gitea.actor }} builds docker image for project 🏗️ +on: + push: + tags: + - "v*" +jobs: + builder: + name: 🏗️ Docker builder release tag 🏗️ + runs-on: dind + steps: + - name: "[docker] registry login" + uses: docker/login-action@v2 + with: + registry: git.cynarski.pl + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + - name: "[code] repository checkout" + uses: actions/checkout@v3 + with: + submodules: recursive + - name: checkout submodule + run: | + git submodule init + git submodule update + - name: "[ledo] Image build" + run: | + ledo image build + - name: "[ledo] Image retag and push" + run: | + export VERSION=$(echo $GITHUB_REF | cut -d '/' -f 3) + ledo image retag latest $VERSION + ledo image push $VERSION + - name: "[docker] system prune" + run: docker system prune -af --volumes diff --git a/.ledo.yml b/.ledo.yml new file mode 100644 index 0000000..bc5eb4e --- /dev/null +++ b/.ledo.yml @@ -0,0 +1,11 @@ +runtime: docker +docker: + registry: git.cynarski.pl + namespace: automancer + name: site + main_service: site + shell: /bin/bash +modes: + base: docker/docker-compose.yml + dev: docker/docker-compose.yml docker/docker-compose.dev.yml + test: docker/docker-compose.yml docker/docker-compose.test.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2ffa920 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# syntax=docker/dockerfile:1.7 + +############################ +# 1) Dependencies +############################ +FROM node:20-alpine AS deps +WORKDIR /app + +RUN apk add --no-cache libc6-compat + +COPY package.json package-lock.json* ./ +RUN npm ci + +############################ +# 2) Build stage +############################ +FROM node:20-alpine AS build +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +RUN npm run build + +############################ +# 3) Production stage (nginx) +############################ +FROM nginx:1.27-alpine AS production + +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/out /usr/share/nginx/html + +EXPOSE 8080 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml new file mode 100644 index 0000000..1c79d1a --- /dev/null +++ b/docker/docker-compose.dev.yml @@ -0,0 +1,17 @@ +version: "3.7" +services: + site: + build: + context: ../ + target: build + volumes: + - "../:/app" + - "/app/node_modules" + - "/app/.next" + ports: + - "3000:3000" + command: npm run dev + environment: + - NODE_ENV=development + networks: + - network-automancer-dev diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..aa832e5 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3.7" +services: + site: + image: git.cynarski.pl/automancer/site:latest + ports: + - "8080:8080" + networks: + - network-automancer-dev + +networks: + network-automancer-dev: {} diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100755 index 0000000..1c206b4 --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -eo pipefail +shopt -s nullglob + +# +# H E L P E R F U N C T I O N S +# +################################## +declare -i term_width=80 + + +h1() { + declare border padding text + border='\e[1;34m'"$(printf '=%.0s' $(seq 1 "$term_width"))"'\e[0m' + padding="$(printf ' %.0s' $(seq 1 $(((term_width - $(wc -m <<<"$*")) / 2))))" + text="\\e[1m$*\\e[0m" + echo -e "$border" + echo -e "${padding}${text}${padding}" + echo -e "$border" +} + +h2() { + printf '\e[1;33m==>\e[37;1m %s\e[0m\n' "$*" +} + +# +# M A I N F U N C T I O N S +# +################################# + +h1 "Project init" + +### +### Your code here +### + +h1 "End of init" + +exec "$@" diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..64faa67 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,29 @@ +server { + listen 8080; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + add_header X-Content-Type-Options nosniff always; + add_header X-Frame-Options DENY always; + add_header Referrer-Policy strict-origin-when-cross-origin always; + + # Static assets: cache aggressively + location ~* \.(?:css|js|mjs|map|png|jpg|jpeg|gif|svg|ico|webp|woff2|woff|ttf)$ { + expires 30d; + add_header Cache-Control "public, max-age=2592000, immutable"; + try_files $uri =404; + } + + # HTML: no cache + location ~* \.html$ { + add_header Cache-Control "no-cache"; + try_files $uri =404; + } + + # Next.js static export: try file, then file.html, then 404 + location / { + try_files $uri $uri.html $uri/ /404.html; + } +} diff --git a/docker/test-entrypoint.sh b/docker/test-entrypoint.sh new file mode 100755 index 0000000..a4b5981 --- /dev/null +++ b/docker/test-entrypoint.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -eo pipefail +shopt -s nullglob + +# +# H E L P E R F U N C T I O N S +# +################################## +declare -i term_width=80 + +h1() { + declare border padding text + border='\e[1;34m'"$(printf '=%.0s' $(seq 1 "$term_width"))"'\e[0m' + padding="$(printf ' %.0s' $(seq 1 $(((term_width - $(wc -m <<<"$*")) / 2))))" + text="\\e[1m$*\\e[0m" + echo -e "$border" + echo -e "${padding}${text}${padding}" + echo -e "$border" +} + +h2() { + printf '\e[1;33m==>\e[37;1m %s\e[0m\n' "$*" +} + +# +# M A I N F U N C T I O N S +# +################################# + +h1 "Project test init" + +### +### Your code here +### + +h1 "End of test init" + +exec "$@" diff --git a/next.config.ts b/next.config.ts index e9ffa30..65c102b 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "export", }; export default nextConfig;