From 6c1e68a14e60f1b145809567347186673026174f Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Thu, 4 Jun 2026 15:26:14 -0400 Subject: [PATCH] chore: add implementation plan for Roc OCI images --- docs/plans/2026-06-04-roc-oci-images-plan.md | 377 +++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 docs/plans/2026-06-04-roc-oci-images-plan.md diff --git a/docs/plans/2026-06-04-roc-oci-images-plan.md b/docs/plans/2026-06-04-roc-oci-images-plan.md new file mode 100644 index 0000000..c8e47ee --- /dev/null +++ b/docs/plans/2026-06-04-roc-oci-images-plan.md @@ -0,0 +1,377 @@ +# Roc OCI Images Implementation Plan + +**Goal:** Build and publish three OCI container images (base, dev, ci) providing the Roc compiler, LSP, and development environment, stored in a single Containerfile with multi-stage targets. + +**Architecture:** Single `Containerfile` with three build targets (`base`, `dev`, `ci`). A shell script downloads the official Roc release tarball from GitHub. A Makefile provides local build/push commands. A Gitea Actions workflow builds and publishes on version tags. + +**Tech Stack:** Docker/podman multi-stage builds, bash, Gitea Actions, GitHub Releases API + +--- + +### Task 1: download-roc.sh script + +**Files:** +- Create: `scripts/download-roc.sh` + +- [ ] **Step 1: Write the download script** + +This script accepts `ROC_VERSION` and `TARGETARCH` and downloads/extracts the corresponding Roc release tarball into `/opt/roc/`. + +```bash +#!/usr/bin/env bash +set -euo pipefail + +ROC_VERSION="${1:?Usage: $0 [target-arch]}" +TARGETARCH="${2:-amd64}" + +# Map Docker buildx arch names to Roc tarball arch names +case "$TARGETARCH" in + amd64) ROC_ARCH="x86_64" ;; + arm64) ROC_ARCH="aarch64" ;; + *) echo "Unsupported architecture: $TARGETARCH" >&2; exit 1 ;; +esac + +TARBALL="roc-linux_${ROC_ARCH}-${ROC_VERSION}.tar.gz" +URL="https://github.com/roc-lang/roc/releases/download/${ROC_VERSION}/${TARBALL}" + +echo "Downloading Roc ${ROC_VERSION} for ${ROC_ARCH}..." +curl -fsSL "$URL" -o /tmp/roc.tar.gz + +echo "Extracting..." +tar -xzf /tmp/roc.tar.gz -C /tmp/ + +# The tarball contains a single directory with an unpredictable nightly name +# e.g. roc_nightly-linux_x86_64-2025-09-09-d73ea109/ +EXTRACTED_DIR=$(find /tmp -maxdepth 1 -type d -name "roc_*" | head -1) +if [ -z "$EXTRACTED_DIR" ]; then + echo "ERROR: Could not find extracted Roc directory in /tmp" >&2 + exit 1 +fi + +echo "Installing to /opt/roc..." +mkdir -p /opt/roc/bin /opt/roc/lib + +install -m 0755 "$EXTRACTED_DIR/roc" /opt/roc/bin/roc + +# LSP may or may not be present in older releases; install if found +if [ -f "$EXTRACTED_DIR/roc_language_server" ]; then + install -m 0755 "$EXTRACTED_DIR/roc_language_server" /opt/roc/bin/roc_language_server +fi + +# WASI libc is needed for some platforms +if [ -d "$EXTRACTED_DIR/lib" ]; then + cp -r "$EXTRACTED_DIR/lib/"* /opt/roc/lib/ +fi + +# License +if [ -f "$EXTRACTED_DIR/LICENSE" ]; then + cp "$EXTRACTED_DIR/LICENSE" /opt/roc/LICENSE +fi + +# Cleanup +rm -rf /tmp/roc.tar.gz "$EXTRACTED_DIR" + +echo "Roc ${ROC_VERSION} installed successfully." +/opt/roc/bin/roc --version +``` + +- [ ] **Step 2: Make it executable and commit** + +```bash +chmod +x scripts/download-roc.sh +git add scripts/download-roc.sh +git commit -m "feat: add Roc release download/extract script" +``` + +--- + +### Task 2: Containerfile — base target + +**Files:** +- Create: `Containerfile` + +- [ ] **Step 1: Write the base stage** + +```dockerfile +# Roc compiler base image +# Provides just the Roc compiler binary and WASI runtime library. + +ARG ROC_VERSION=0.0.0-alpha2 + +FROM debian:bookworm-slim AS curl-stage +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/* +ARG ROC_VERSION +ARG TARGETARCH +COPY scripts/download-roc.sh /tmp/download-roc.sh +RUN /tmp/download-roc.sh "$ROC_VERSION" "$TARGETARCH" + +FROM debian:bookworm-slim AS base +ARG ROC_VERSION +ARG TARGETARCH +LABEL org.opencontainers.image.title="roc-tools-base" +LABEL org.opencontainers.image.description="Roc compiler base image" +LABEL org.opencontainers.image.version="${ROC_VERSION}" +LABEL org.opencontainers.image.source="https://git.roo.lol/jbrechtel/roc-lang-tools" + +RUN groupadd --gid 1000 roc && useradd --uid 1000 --gid roc --shell /bin/sh --create-home roc + +COPY --from=curl-stage /opt/roc /opt/roc +ENV PATH="/opt/roc/bin:${PATH}" + +USER roc +WORKDIR /home/roc +ENTRYPOINT ["roc"] +CMD ["--help"] +``` + +- [ ] **Step 2: Commit** + +```bash +git add Containerfile +git commit -m "feat: add Containerfile with base build target" +``` + +--- + +### Task 3: Containerfile — dev target + +- [ ] **Step 1: Add dev stage to Containerfile** + +Append to Containerfile: + +```dockerfile +FROM base AS dev +LABEL org.opencontainers.image.title="roc-tools-dev" +LABEL org.opencontainers.image.description="Roc development environment with LSP and dev tools" +LABEL org.opencontainers.image.version="${ROC_VERSION}" + +USER root +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + curl \ + bash \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +USER roc +ENTRYPOINT ["bash"] +``` + +- [ ] **Step 2: Commit** + +```bash +git add Containerfile +git commit -m "feat: add dev build target to Containerfile" +``` + +--- + +### Task 4: Containerfile — ci target + +- [ ] **Step 1: Add ci stage to Containerfile** + +Append to Containerfile: + +```dockerfile +FROM base AS ci +LABEL org.opencontainers.image.title="roc-tools-ci" +LABEL org.opencontainers.image.description="Roc CI/CD runner image" +LABEL org.opencontainers.image.version="${ROC_VERSION}" + +USER root +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +USER roc +ENTRYPOINT ["roc"] +CMD ["--help"] +``` + +- [ ] **Step 2: Commit** + +```bash +git add Containerfile +git commit -m "feat: add ci build target to Containerfile" +``` + +--- + +### Task 5: Makefile + +**Files:** +- Create: `Makefile` + +- [ ] **Step 1: Write the Makefile** + +```makefile +ROC_VERSION ?= 0.0.0-alpha2 +REGISTRY ?= git.roo.lol/jbrechtel + +.PHONY: build-base build-dev build-ci build-all push-all + +build-base: + docker build \ + --target base \ + --build-arg ROC_VERSION=$(ROC_VERSION) \ + -t $(REGISTRY)/roc-tools-base:$(ROC_VERSION) \ + -t $(REGISTRY)/roc-tools-base:latest \ + . + +build-dev: + docker build \ + --target dev \ + --build-arg ROC_VERSION=$(ROC_VERSION) \ + -t $(REGISTRY)/roc-tools-dev:$(ROC_VERSION) \ + -t $(REGISTRY)/roc-tools-dev:latest \ + . + +build-ci: + docker build \ + --target ci \ + --build-arg ROC_VERSION=$(ROC_VERSION) \ + -t $(REGISTRY)/roc-tools-ci:$(ROC_VERSION) \ + -t $(REGISTRY)/roc-tools-ci:latest \ + . + +build-all: build-base build-dev build-ci + +push-base: + docker push $(REGISTRY)/roc-tools-base:$(ROC_VERSION) + docker push $(REGISTRY)/roc-tools-base:latest + +push-dev: + docker push $(REGISTRY)/roc-tools-dev:$(ROC_VERSION) + docker push $(REGISTRY)/roc-tools-dev:latest + +push-ci: + docker push $(REGISTRY)/roc-tools-ci:$(ROC_VERSION) + docker push $(REGISTRY)/roc-tools-ci:latest + +push-all: build-all push-base push-dev push-ci +``` + +- [ ] **Step 2: Commit** + +```bash +git add Makefile +git commit -m "feat: add Makefile with build and push targets" +``` + +--- + +### Task 6: Gitea Actions workflow + +**Files:** +- Create: `.gitea/workflows/build.yaml` + +- [ ] **Step 1: Write the workflow** + +```yaml +name: Build and Publish OCI Images + +on: + push: + tags: + - 'v*' + +env: + REGISTRY: git.roo.lol + REGISTRY_USER: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_TOKEN }} + IMAGE_NAMESPACE: ${{ github.repository_owner }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract Roc version from tag + id: version + run: | + # Strip leading 'v' from tag name: v0.0.0-alpha2 -> 0.0.0-alpha2 + ROC_VERSION="${GITHUB_REF_NAME#v}" + echo "roc_version=${ROC_VERSION}" >> "$GITHUB_OUTPUT" + echo "ROC_VERSION=${ROC_VERSION}" >> "$GITHUB_ENV" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + run: | + echo "$REGISTRY_PASSWORD" | docker login $REGISTRY -u $REGISTRY_USER --password-stdin + + - name: Build and push base image + uses: docker/build-push-action@v6 + with: + context: . + target: base + build-args: ROC_VERSION=${{ steps.version.outputs.roc_version }} + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/roc-tools-base:${{ steps.version.outputs.roc_version }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/roc-tools-base:latest + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push dev image + uses: docker/build-push-action@v6 + with: + context: . + target: dev + build-args: ROC_VERSION=${{ steps.version.outputs.roc_version }} + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/roc-tools-dev:${{ steps.version.outputs.roc_version }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/roc-tools-dev:latest + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build and push ci image + uses: docker/build-push-action@v6 + with: + context: . + target: ci + build-args: ROC_VERSION=${{ steps.version.outputs.roc_version }} + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/roc-tools-ci:${{ steps.version.outputs.roc_version }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/roc-tools-ci:latest + push: true + cache-from: type=gha + cache-to: type=gha,mode=max +``` + +- [ ] **Step 2: Commit** + +```bash +mkdir -p .gitea/workflows +git add .gitea/workflows/build.yaml +git commit -m "feat: add Gitea Actions workflow for building and publishing OCI images" +``` + +--- + +### Task 7: Add .gitignore + +**Files:** +- Create: `.gitignore` + +- [ ] **Step 1: Write .gitignore** + +``` +*.tar.gz +/tmp/ +``` + +- [ ] **Step 2: Commit** + +```bash +git add .gitignore +git commit -m "chore: add .gitignore" +```