feat(ci): add Docker build pipeline with version management
- Add multi-stage Dockerfile with pinned tool versions (Node 24.10.0, Go 1.25.1, Hugo 0.151.0) - Create .env.versions as single source of truth for all tool versions - Add GitHub Actions CI workflow for automated OCI image builds - Multi-arch support (amd64, arm64) - Automatic version loading from .env.versions - Docker registry push with metadata tags - Add Taskfile tasks for local OCI image building and testing - task build:oci-image - Build with version-pinned dependencies - task test:oci-image - Build and test container locally - Pin devbox.json to specific versions matching .env.versions - Add comprehensive documentation (DOCKER.md, VERSIONS.md) - Add helper script (scripts/get-versions.sh) for version extraction This enables consistent development and production environments with identical tool versions across local devbox, Docker builds, and CI/CD.
This commit is contained in:
parent
8e0aea2893
commit
4294524e81
9 changed files with 406 additions and 3 deletions
19
.dockerignore
Normal file
19
.dockerignore
Normal file
|
|
@ -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
|
||||||
9
.env.versions
Normal file
9
.env.versions
Normal file
|
|
@ -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
|
||||||
80
.github/workflows/ci.yaml
vendored
Normal file
80
.github/workflows/ci.yaml
vendored
Normal file
|
|
@ -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 }}
|
||||||
98
DOCKER.md
Normal file
98
DOCKER.md
Normal file
|
|
@ -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
|
||||||
66
Dockerfile
Normal file
66
Dockerfile
Normal file
|
|
@ -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;"]
|
||||||
34
Taskfile.yml
34
Taskfile.yml
|
|
@ -86,3 +86,37 @@ tasks:
|
||||||
desc: Run CI pipeline locally
|
desc: Run CI pipeline locally
|
||||||
deps:
|
deps:
|
||||||
- test
|
- 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
|
||||||
|
|
|
||||||
69
VERSIONS.md
Normal file
69
VERSIONS.md
Normal file
|
|
@ -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.
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.10.5/.schema/devbox.schema.json",
|
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.10.5/.schema/devbox.schema.json",
|
||||||
"packages": [
|
"packages": [
|
||||||
"hugo@latest",
|
"hugo@0.151.0",
|
||||||
"dart-sass@latest",
|
"dart-sass@latest",
|
||||||
"go@latest",
|
"go@1.25.1",
|
||||||
"nodejs@latest",
|
"nodejs@24.10.0",
|
||||||
"htmltest@latest",
|
"htmltest@latest",
|
||||||
"go-task@latest"
|
"go-task@latest"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
28
scripts/get-versions.sh
Executable file
28
scripts/get-versions.sh
Executable file
|
|
@ -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 ."
|
||||||
Loading…
Add table
Add a link
Reference in a new issue