diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5e7f187 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.github +.gitignore +.vscode +.devbox +.devcontainer +node_modules +public +resources +tmp +*.md +!content/**/*.md +TESTING.md +Taskfile.yml +devbox.json +devbox.lock +.hugo_build.lock +.htmltest.yml +.htmlvalidate.json +.markdownlint.json diff --git a/.env.versions b/.env.versions new file mode 100644 index 0000000..25ceae4 --- /dev/null +++ b/.env.versions @@ -0,0 +1,9 @@ +# Tool versions for development and CI/CD +# These versions are used in: +# - devbox.json (pinned versions) +# - Dockerfile (build arguments) +# - .github/workflows/ci.yaml (CI/CD pipeline) + +NODE_VERSION=24.10.0 +GO_VERSION=1.25.1 +HUGO_VERSION=0.151.0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..d6e4a29 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,80 @@ +name: ci + +on: + push: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Load versions from .env.versions + id: versions + run: | + # Source the versions file + set -a + source .env.versions + set +a + + echo "node_version=${NODE_VERSION}" >> "$GITHUB_OUTPUT" + echo "go_version=${GO_VERSION}" >> "$GITHUB_OUTPUT" + echo "hugo_version=${HUGO_VERSION}" >> "$GITHUB_OUTPUT" + + echo "Node: ${NODE_VERSION}" + echo "Go: ${GO_VERSION}" + echo "Hugo: ${HUGO_VERSION}" + + - name: Repository meta + id: repository + run: | + registry=${{ github.server_url }} + registry=${registry##http*://} + echo "registry=${registry}" >> "$GITHUB_OUTPUT" + echo "registry=${registry}" + repository="$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')" + echo "repository=${repository}" >> "$GITHUB_OUTPUT" + echo "repository=${repository}" + + - name: Docker meta + uses: docker/metadata-action@v5 + id: docker + with: + images: ${{ steps.repository.outputs.registry }}/${{ steps.repository.outputs.repository }} + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ steps.repository.outputs.registry }} + username: "${{ secrets.PACKAGES_USER }}" + password: "${{ secrets.PACKAGES_TOKEN }}" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + buildkitd-flags: '--allow-insecure-entitlement network.host' + driver-opts: network=host + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + allow: network.host + network: host + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.docker.outputs.tags }} + labels: ${{ steps.docker.outputs.labels }} + build-args: | + NODE_VERSION=${{ steps.versions.outputs.node_version }} + GO_VERSION=${{ steps.versions.outputs.go_version }} + HUGO_VERSION=${{ steps.versions.outputs.hugo_version }} diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..2ff9b90 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,98 @@ +# Docker Build + +This project uses a multi-stage Docker build that matches the local devbox development environment. + +## Version Management + +All tool versions are defined in `.env.versions` as the single source of truth: + +```bash +NODE_VERSION=24.10.0 +GO_VERSION=1.25.1 +HUGO_VERSION=0.151.0 +``` + +These versions are used in: + +- `devbox.json` - Local development environment +- `Dockerfile` - Docker build arguments (with defaults) +- `.github/workflows/ci.yaml` - CI/CD pipeline + +**Important:** When updating versions, modify `.env.versions` and sync with `devbox.json`. + +## Local Build + +### Using Task (recommended) + +The easiest way to build the OCI image: + +```bash +task build:oci-image +``` + +This automatically: + +- Loads versions from `.env.versions` +- Builds the image with correct build arguments +- Tags with `latest` and git commit hash + +To build and test: + +```bash +task test:oci-image +``` + +### Automatic version loading + +Use the helper script to load versions from `.env.versions`: + +```bash +source scripts/get-versions.sh +``` + +This will show you the Docker build command with the correct versions. + +### Manual build + +```bash +docker build --network=host \ + --build-arg NODE_VERSION=24.10.0 \ + --build-arg GO_VERSION=1.25.1 \ + --build-arg HUGO_VERSION=0.151.0 \ + -t ipceicis-developerframework:latest . +``` + +### Test the image + +```bash +docker run -d -p 8080:80 --name hugo-test ipceicis-developerframework:latest +curl http://localhost:8080 +docker stop hugo-test && docker rm hugo-test +``` + +## CI/CD Pipeline + +The GitHub Actions workflow (`.github/workflows/ci.yaml`) automatically: + +1. Extracts versions from devbox environment +2. Builds multi-arch images (amd64 + arm64) +3. Pushes to the container registry with appropriate tags + +### Required Secrets + +Configure these secrets in your GitHub repository: + +- `PACKAGES_USER`: Container registry username +- `PACKAGES_TOKEN`: Container registry token/password + +## Image Structure + +- **Build Stage**: Uses Node.js base image, installs Go and Hugo +- **Runtime Stage**: Uses nginx:alpine to serve static content (~50MB) + +The build process: + +1. Installs npm dependencies +2. Downloads Hugo modules +3. Builds static site with `hugo --gc --minify` +4. Copies built site to minimal nginx container diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..74b68a0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +# Build arguments for version pinning (matching devbox.json) +ARG NODE_VERSION=24.10.0 +ARG GO_VERSION=1.25.1 +ARG HUGO_VERSION=0.151.0 + +# Build stage - use same versions as local devbox environment +FROM node:${NODE_VERSION}-bookworm AS builder + +# Install Git (needed for Hugo's enableGitInfo) +RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* + +# Install Go +ARG GO_VERSION +RUN wget -q https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz && \ + tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz && \ + rm go${GO_VERSION}.linux-amd64.tar.gz + +ENV PATH="/usr/local/go/bin:${PATH}" +ENV GOPATH="/go" +ENV PATH="${GOPATH}/bin:${PATH}" + +# Install Hugo extended +ARG HUGO_VERSION +RUN wget -q https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz && \ + tar -xzf hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz && \ + mv hugo /usr/local/bin/ && \ + rm hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz && \ + hugo version + +WORKDIR /src + +# Copy package files and install npm dependencies +COPY package*.json ./ +RUN npm ci + +# Copy all source files +COPY . . + +# Build Hugo site (Git info wird aus dem aktuellen Kontext genommen, nicht aus .git) +# Hugo sucht nach .git, findet es nicht, und überspringt Git-Info automatisch +RUN hugo --gc --minify + +# Runtime stage - nginx to serve static content +FROM nginx:1.27-alpine + +# Copy built site from builder +COPY --from=builder /src/public /usr/share/nginx/html + +# Copy custom nginx config +RUN echo 'server {' > /etc/nginx/conf.d/default.conf && \ + echo ' listen 80;' >> /etc/nginx/conf.d/default.conf && \ + echo ' server_name _;' >> /etc/nginx/conf.d/default.conf && \ + echo ' root /usr/share/nginx/html;' >> /etc/nginx/conf.d/default.conf && \ + echo ' index index.html;' >> /etc/nginx/conf.d/default.conf && \ + echo '' >> /etc/nginx/conf.d/default.conf && \ + echo ' location / {' >> /etc/nginx/conf.d/default.conf && \ + echo ' try_files $uri $uri/ /index.html;' >> /etc/nginx/conf.d/default.conf && \ + echo ' }' >> /etc/nginx/conf.d/default.conf && \ + echo '' >> /etc/nginx/conf.d/default.conf && \ + echo ' gzip on;' >> /etc/nginx/conf.d/default.conf && \ + echo ' gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;' >> /etc/nginx/conf.d/default.conf && \ + echo '}' >> /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/Taskfile.yml b/Taskfile.yml index ffb5199..5592f19 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -86,3 +86,37 @@ tasks: desc: Run CI pipeline locally deps: - test + + build:oci-image: + desc: Build OCI/Docker image with versions from .env.versions + cmds: + - | + set -a + source .env.versions + set +a + echo "Building OCI image with versions:" + echo " NODE_VERSION=${NODE_VERSION}" + echo " GO_VERSION=${GO_VERSION}" + echo " HUGO_VERSION=${HUGO_VERSION}" + docker build --network=host \ + --build-arg NODE_VERSION=${NODE_VERSION} \ + --build-arg GO_VERSION=${GO_VERSION} \ + --build-arg HUGO_VERSION=${HUGO_VERSION} \ + -t ipceicis-developerframework:latest \ + -t ipceicis-developerframework:$(git rev-parse --short HEAD) \ + . + + test:oci-image: + desc: Test the built OCI image + deps: + - build:oci-image + cmds: + - | + echo "Starting container on port 8080..." + docker run -d -p 8080:80 --name hugo-test ipceicis-developerframework:latest + sleep 2 + echo "Testing endpoint..." + curl -f http://localhost:8080 > /dev/null && echo "✓ Container is running and responding" || echo "✗ Container test failed" + echo "Cleaning up..." + docker stop hugo-test + docker rm hugo-test diff --git a/VERSIONS.md b/VERSIONS.md new file mode 100644 index 0000000..f1f8401 --- /dev/null +++ b/VERSIONS.md @@ -0,0 +1,69 @@ +# Version Management + +## Single Source of Truth: `.env.versions` + +All tool versions are centrally managed in `.env.versions`: + +```bash +NODE_VERSION=24.10.0 +GO_VERSION=1.25.1 +HUGO_VERSION=0.151.0 +``` + +## Where are versions used? + +1. **devbox.json** - Local development environment (manual sync required) +2. **Dockerfile** - Build arguments with defaults +3. **.github/workflows/ci.yaml** - CI/CD pipeline (automatic) +4. **scripts/get-versions.sh** - Helper script for local builds + +## Updating Versions + +### Step 1: Update `.env.versions` + +Edit the file with new versions: + +```bash +NODE_VERSION=24.12.0 +GO_VERSION=1.25.2 +HUGO_VERSION=0.152.0 +``` + +### Step 2: Update `devbox.json` + +Manually sync the versions in `devbox.json`: + +```json +{ + "packages": [ + "hugo@0.152.0", + "go@1.25.2", + "nodejs@24.12.0", + ... + ] +} +``` + +### Step 3: Rebuild devbox environment + +```bash +devbox shell --refresh +``` + +### Step 4: Test Docker build + +```bash +source scripts/get-versions.sh +# Follow the printed docker build command +``` + +## Why not automatic devbox sync? + +- devbox.json uses a different version format (e.g., `@latest` vs specific versions) +- devbox package names may differ from Docker image names +- Keeps devbox.json simple and readable +- Manual sync ensures intentional version updates + +## CI/CD + +The GitHub Actions workflow automatically loads versions from `.env.versions` - no manual intervention needed. diff --git a/devbox.json b/devbox.json index 56ed1a3..4c3241a 100644 --- a/devbox.json +++ b/devbox.json @@ -1,10 +1,10 @@ { "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.10.5/.schema/devbox.schema.json", "packages": [ - "hugo@latest", + "hugo@0.151.0", "dart-sass@latest", - "go@latest", - "nodejs@latest", + "go@1.25.1", + "nodejs@24.10.0", "htmltest@latest", "go-task@latest" ], diff --git a/scripts/get-versions.sh b/scripts/get-versions.sh new file mode 100755 index 0000000..cc90f56 --- /dev/null +++ b/scripts/get-versions.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Load versions from .env.versions for Docker build +# Usage: source scripts/get-versions.sh + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VERSIONS_FILE="${SCRIPT_DIR}/../.env.versions" + +if [ ! -f "$VERSIONS_FILE" ]; then + echo "Error: .env.versions not found at $VERSIONS_FILE" + exit 1 +fi + +# Load versions +set -a +source "$VERSIONS_FILE" +set +a + +echo "Loaded versions from .env.versions:" +echo " NODE_VERSION=${NODE_VERSION}" +echo " GO_VERSION=${GO_VERSION}" +echo " HUGO_VERSION=${HUGO_VERSION}" +echo "" +echo "Build Docker image with:" +echo " docker build --network=host \\" +echo " --build-arg NODE_VERSION=${NODE_VERSION} \\" +echo " --build-arg GO_VERSION=${GO_VERSION} \\" +echo " --build-arg HUGO_VERSION=${HUGO_VERSION} \\" +echo " -t ipceicis-developerframework:latest ."