Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Keep the build context lean and reproducible. Excluding .git also disables
# main.go's VCS build-info fallback, which is why docker.yml passes VERSION as a
# build-arg instead.
.git
.github
.gitignore
.gitattributes
.dockerignore
Dockerfile
*.md
docs
deploy

# Editor / tooling
.vscode
.idea
.claude

# Server config carries secrets (secrets.key, SMTP/ZPA tokens) — never in an image.
.glabs-web.yaml
.glabs.yaml

# Untracked per-participant working data must never enter the build context.
courseid_*
ob-vss-*
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,16 @@ jobs:
- test-fast
permissions:
contents: write
# actions: write lets the trigger step below dispatch docker.yml.
actions: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: true
- name: Record the latest tag before the release
id: before
run: echo "tag=$(git tag --sort=-v:refname | head -n1)" >> "$GITHUB_OUTPUT"
- uses: go-semantic-release/action@v1
id: semrel
with:
Expand All @@ -114,3 +119,18 @@ jobs:
hooks: goreleaser
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Trigger the glabs-web Docker image build on a new release
# A GITHUB_TOKEN-authored release does not fire the `release` event, so
# dispatch docker.yml ourselves when semantic-release created a new tag.
# workflow_dispatch IS allowed to run when invoked with GITHUB_TOKEN.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git fetch --tags --force
new_tag=$(git tag --sort=-v:refname | head -n1)
if [ -n "$new_tag" ] && [ "$new_tag" != "${{ steps.before.outputs.tag }}" ]; then
echo "New release tag $new_tag — dispatching docker.yml"
gh workflow run docker.yml -f tag="$new_tag"
else
echo "No new tag (was: ${{ steps.before.outputs.tag }}, now: $new_tag) — skipping Docker build"
fi
67 changes: 67 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Create and publish the glabs-web Docker image

on:
# Triggered explicitly from ci.yml after a release (go-semantic-release creates
# the release with GITHUB_TOKEN, which does not fire the `release` event). The
# `release` trigger is kept for a manually published release.
workflow_dispatch:
inputs:
tag:
description: "Release tag to build (e.g. v3.22.0)"
required: true
type: string
release:
types: [published]

env:
REGISTRY: ghcr.io
# The CLI repo is obcode/glabs; the web server image is published as glabs-web.
IMAGE_NAME: ${{ github.repository }}-web

jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# The release tag to build, from whichever trigger fired.
env:
VERSION: ${{ github.event.release.tag_name || inputs.tag }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# Build the released code, not whatever is on main now.
ref: ${{ github.event.release.tag_name || inputs.tag }}

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Tag the image with the release tag and latest (works for both triggers).
tags: |
type=raw,value=${{ env.VERSION }}
type=raw,value=latest

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Version metadata into the build (main.go ldflags via the Dockerfile).
build-args: |
VERSION=${{ env.VERSION }}

# The deploy job (self-hosted runner, on-host docker-compose) comes with the
# deploy/ stack (Caddyfile + docker-compose.yml), the second Milestone-F PR.
40 changes: 40 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# glabs-web — the GraphQL server behind glabs.cs.hm.edu.
#
# The repo builds TWO binaries from one Go module (the `glabs` CLI at `.` and
# `glabs-web` at `./cmd/glabs-web`); this image builds ONLY the web server.
# The mail templates are embedded via //go:embed (web/mail/tmpl), so no assets
# need to be copied into the runtime stage.
FROM golang:1.26-alpine AS builder
WORKDIR /src

# Version metadata, passed by docker.yml from the release tag (mirrors the
# goreleaser ldflags used for the CLI). .git is excluded via .dockerignore, so
# main.go's debug.ReadBuildInfo VCS fallback cannot fill these — hence the ARGs.
ARG VERSION=dev
ARG COMMIT=none
ARG DATE=unknown

# Cache the module graph separately from the source.
COPY go.mod go.sum ./
RUN go mod download

COPY . .
# CGO off → a static binary that runs on the bare alpine runtime below.
RUN CGO_ENABLED=0 GOOS=linux go build \
-trimpath \
-ldflags "-s -w -X 'main.version=${VERSION}' -X 'main.commit=${COMMIT}' -X 'main.date=${DATE}' -X 'main.builtBy=docker'" \
-o /out/glabs-web ./cmd/glabs-web

# --- Runtime ---
FROM alpine:3.21
# ca-certificates: TLS to GitLab / ZPA / SMTP. tzdata: main() sets time.Local to
# Europe/Berlin at startup and needs the zoneinfo database.
RUN apk add --no-cache ca-certificates tzdata \
&& adduser -D -u 10001 glabs
WORKDIR /app
COPY --from=builder /out/glabs-web /usr/local/bin/glabs-web
USER glabs
EXPOSE 8080
# .glabs-web.yaml is searched in "." (this WORKDIR) first, then $HOME — the
# deploy bind-mounts it read-only into /app. server.port defaults to 8080.
ENTRYPOINT ["glabs-web"]
Loading