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
|
||||
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
|
||||
|
|
|
|||
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",
|
||||
"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"
|
||||
],
|
||||
|
|
|
|||
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