Add Docker support with Dockerfile, entrypoint script, and CI workflows
CI / Go Tests (push) Failing after 26s
CI / Build (push) Successful in 15s
Format / gofmt (push) Successful in 5s

- Introduced a Dockerfile for building the application with Go and Node.js dependencies.
- Added a docker-entrypoint.sh script to manage container execution.
- Created a .dockerignore file to exclude unnecessary files from the Docker context.
- Updated Makefile to include a new target for building the Docker image.
- Added CI workflows for testing, formatting, and releasing artifacts in Gitea.
This commit is contained in:
2026-06-01 02:03:17 -05:00
parent 050ec138f3
commit 34fd1a5a4d
8 changed files with 295 additions and 2 deletions
+7
View File
@@ -0,0 +1,7 @@
.git
.gitea
agent-transcripts
bin
data
node_modules
web/ui/node_modules
+53
View File
@@ -0,0 +1,53 @@
name: CI
on:
push:
branches:
- "**"
pull_request:
jobs:
test:
name: Go Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Run tests
run: make test
- name: Run race tests
run: make test-race
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: web/ui/package-lock.json
- name: Install UI deps
run: npm --prefix ./web/ui ci
- name: Build
run: make build
+31
View File
@@ -0,0 +1,31 @@
name: Format
on:
push:
branches:
- "**"
pull_request:
jobs:
gofmt:
name: gofmt
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Verify formatting
shell: bash
run: |
files="$(gofmt -l .)"
if [[ -n "${files}" ]]; then
echo "These files need formatting:"
echo "${files}"
exit 1
fi
+92
View File
@@ -0,0 +1,92 @@
name: Release Artifacts
on:
push:
tags:
- "*"
workflow_dispatch:
jobs:
build:
name: Build ${{ matrix.goos }}/${{ matrix.goarch }} (${{ matrix.variant }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
variant: default
static: false
- goos: linux
goarch: amd64
variant: alpine-static
static: true
- goos: linux
goarch: arm64
variant: default
static: false
- goos: linux
goarch: arm64
variant: alpine-static
static: true
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: web/ui/package-lock.json
- name: Install UI deps
run: npm --prefix ./web/ui ci
- name: Build UI assets
run: npm --prefix ./web/ui run build
- name: Build binary
shell: bash
run: |
mkdir -p dist
if [[ "${{ matrix.static }}" == "true" ]]; then
CGO_ENABLED=0 GOOS="${{ matrix.goos }}" GOARCH="${{ matrix.goarch }}" \
go build -trimpath -tags "netgo osusergo" \
-ldflags "-s -w" \
-o "dist/scratchbox-${{ matrix.goos }}-${{ matrix.goarch }}-${{ matrix.variant }}" \
./cmd/scratchbox
else
CGO_ENABLED=0 GOOS="${{ matrix.goos }}" GOARCH="${{ matrix.goarch }}" \
go build -trimpath \
-ldflags "-s -w" \
-o "dist/scratchbox-${{ matrix.goos }}-${{ matrix.goarch }}-${{ matrix.variant }}" \
./cmd/scratchbox
fi
- name: Verify static linux binary
if: ${{ matrix.static }}
shell: bash
run: |
file "dist/scratchbox-${{ matrix.goos }}-${{ matrix.goarch }}-${{ matrix.variant }}" | grep -q "statically linked"
- name: Package artifact
shell: bash
run: |
mkdir -p artifacts
cp README.md artifacts/
cp "dist/scratchbox-${{ matrix.goos }}-${{ matrix.goarch }}-${{ matrix.variant }}" artifacts/
tar -C artifacts -czf "scratchbox-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }}-${{ matrix.variant }}.tar.gz" .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: scratchbox-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }}-${{ matrix.variant }}
path: scratchbox-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }}-${{ matrix.variant }}.tar.gz
+39
View File
@@ -0,0 +1,39 @@
FROM golang:1.25-alpine AS builder
WORKDIR /src
# Build-time dependencies for UI and Go compilation.
RUN apk add --no-cache nodejs npm
COPY go.mod go.sum ./
RUN go mod download
COPY web/ui/package.json web/ui/package-lock.json ./web/ui/
RUN npm --prefix ./web/ui ci
COPY . .
RUN npm --prefix ./web/ui run build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -tags "netgo osusergo" -ldflags "-s -w" \
-o /out/scratchbox ./cmd/scratchbox
FROM alpine:3.22
RUN addgroup -S scratchbox && adduser -S -G scratchbox scratchbox
WORKDIR /app
COPY --from=builder /out/scratchbox /usr/local/bin/scratchbox
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh && \
mkdir -p /data /config && chown -R scratchbox:scratchbox /data /config
USER scratchbox
EXPOSE 8080
VOLUME ["/data", "/config"]
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
+7 -2
View File
@@ -3,8 +3,9 @@ CMD_DIR := ./cmd/scratchbox
BIN_DIR := ./bin
BIN := $(BIN_DIR)/$(APP_NAME)
CONFIG ?= config.yaml
DOCKER_IMAGE ?= scratchbox:dev
.PHONY: help run dev config build test test-race fmt clean
.PHONY: help run dev config build docker test test-race fmt clean
help:
@echo "Targets:"
@@ -12,6 +13,7 @@ help:
@echo " make dev - Build and run server (CONFIG=$(CONFIG))"
@echo " make config - Write default config to $(CONFIG) and generate sibling config.key if missing"
@echo " make build - Build Svelte UI and binary to $(BIN)"
@echo " make docker - Build Docker image (DOCKER_IMAGE=$(DOCKER_IMAGE))"
@echo " make test - Run all Go tests with timeout and shuffle"
@echo " make test-race - Run all Go tests with race detector and timeout"
@echo " make fmt - Format Go code"
@@ -32,7 +34,10 @@ config:
build: clean
npm --prefix ./web/ui run build
mkdir -p $(BIN_DIR)
go build -o $(BIN) $(CMD_DIR)
CGO_ENABLED=0 go build -o $(BIN) $(CMD_DIR)
docker:
docker build -t $(DOCKER_IMAGE) .
test:
go test -timeout 30s -shuffle=on ./...
+54
View File
@@ -23,6 +23,60 @@ Without `--config`, built-in defaults are used.
If you modify the web UI source (`web/ui`), rebuild frontend assets with:
- `make build`
## Docker
Build the container image:
- `make docker`
- Default image tag: `scratchbox:dev`
Optionally set a custom image name/tag:
- `make docker DOCKER_IMAGE=scratchbox:latest`
Run with persistent data and config directories:
- `mkdir -p ./docker-data ./docker-config`
- `docker run --rm -p 8080:8080 -v "$(pwd)/docker-data:/data" -v "$(pwd)/docker-config:/config" scratchbox:dev`
Inside the container, the default command is:
- `scratchbox server --config /config/config.yaml`
Create config files with the image (writes to mounted `./docker-config`):
- `docker run --rm -v "$(pwd)/docker-config:/config" scratchbox:dev generate-config > ./docker-config/config.yaml`
- `docker run --rm -v "$(pwd)/docker-config:/config" scratchbox:dev generate-key > ./docker-config/config.key`
## Release Artifacts
Tagged releases provide `.tar.gz` archives with prebuilt Linux binaries.
Variant guide:
| Variant | Recommended for | Notes |
| --- | --- | --- |
| `default` | Most Linux distributions (Ubuntu, Debian, Fedora, Arch, etc.) | Smaller, dynamically linked Linux build |
| `alpine-static` | Alpine Linux / musl-based environments | Statically linked for better compatibility |
To unpack an artifact:
- `tar -xzf scratchbox-<tag>-linux-amd64-default.tar.gz`
To unpack into a specific directory:
- `mkdir -p ./scratchbox-release`
- `tar -xzf scratchbox-<tag>-linux-amd64-default.tar.gz -C ./scratchbox-release`
To run from an unpacked artifact:
- `./scratchbox-<os>-<arch>-<variant> server --config config.yaml`
If you do not have a config file yet, generate one first:
- `go run ./cmd/scratchbox generate-config > config.yaml`
- `go run ./cmd/scratchbox generate-key > config.key`
## Configuration
All configuration is YAML.
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
set -eu
# Default to running the server with mounted config.
if [ "$#" -eq 0 ]; then
set -- server --config /config/config.yaml
elif [ "${1#-}" != "$1" ]; then
# Allow passing server flags directly, e.g. `docker run ... --config ...`.
set -- server "$@"
fi
exec scratchbox "$@"