From 15918bdb18d2628f9a9a835e1796c2fce5c4a042 Mon Sep 17 00:00:00 2001 From: Jerry Zhao Date: Tue, 30 Jun 2026 15:25:51 +0000 Subject: [PATCH 1/2] Add apt-only ubuntu-dev image with multi-arch CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an Ubuntu 24.04 development image (eloqdata/ubuntu-dev) whose dependencies are installed entirely via apt — no from-source builds, no baked secrets, runs as non-root user eloq with passwordless sudo. Add a GitHub Actions workflow that builds on native amd64 and arm64 runners: PRs validate both arches (no push), pushes to main build, push per-arch, and merge into a multi-arch manifest on DockerHub. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ubuntu-dev.yml | 116 +++++++++++++++++++++++++++++++ README.md | 45 +++++++++++- ubuntu-dev/Dockerfile | 43 ++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ubuntu-dev.yml create mode 100644 ubuntu-dev/Dockerfile diff --git a/.github/workflows/ubuntu-dev.yml b/.github/workflows/ubuntu-dev.yml new file mode 100644 index 0000000..95cee65 --- /dev/null +++ b/.github/workflows/ubuntu-dev.yml @@ -0,0 +1,116 @@ +name: ubuntu-dev + +# Pull requests build the image on amd64 and arm64 for validation only (no login, no push). +# Pushes to main build both arches and publish a multi-arch manifest to DockerHub. +on: + pull_request: + paths: + - 'ubuntu-dev/**' + - '.github/workflows/ubuntu-dev.yml' + push: + branches: [main] + paths: + - 'ubuntu-dev/**' + - '.github/workflows/ubuntu-dev.yml' + workflow_dispatch: + +env: + IMAGE: eloqdata/ubuntu-dev + +jobs: + build: + name: build (${{ matrix.platform }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Login (and therefore the password) is only used off pull requests. + # Fork PRs never receive secrets, so PR validation never touches credentials. + - name: Log in to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest + type=raw,value=24.04 + type=sha,prefix=sha- + + - name: Generate arch-suffixed tags + id: suffixed_tags + shell: bash + run: | + arch=$(echo "${{ matrix.platform }}" | cut -d/ -f2) + echo "tags<> "$GITHUB_OUTPUT" + while IFS= read -r tag; do + [ -z "$tag" ] && continue + echo "${tag}-${arch}" >> "$GITHUB_OUTPUT" + done <<< "${{ steps.meta.outputs.tags }}" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: ./ubuntu-dev + platforms: ${{ matrix.platform }} + # PR: build only (validation). main: push the arch-suffixed image. + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.suffixed_tags.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=${{ matrix.platform }}-ubuntu-dev + cache-to: type=gha,mode=max,scope=${{ matrix.platform }}-ubuntu-dev + + merge: + name: merge manifest + if: github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-24.04 + steps: + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata (tags) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest + type=raw,value=24.04 + type=sha,prefix=sha- + + - name: Create and push manifest list + shell: bash + run: | + while IFS= read -r tag; do + [ -z "$tag" ] && continue + echo "Merging manifests for $tag" + docker buildx imagetools create -t "$tag" \ + "${tag}-amd64" \ + "${tag}-arm64" + done <<< "${{ steps.meta.outputs.tags }}" + + - name: Inspect + run: docker buildx imagetools inspect ${{ env.IMAGE }}:24.04 diff --git a/README.md b/README.md index d2a87bd..85b5d20 100644 --- a/README.md +++ b/README.md @@ -1 +1,44 @@ -# eloq-docker \ No newline at end of file +# eloq-docker + +Docker images for EloqData development. + +## Images + +### `eloqdata/ubuntu-dev` + +A lightweight Ubuntu 24.04 development image containing the build toolchain and libraries that +EloqData projects need, installed entirely via `apt` (compilers, CMake/Ninja, common C/C++ dev +libraries, Python 3, Node.js, Go, the JDK, and the Google Cloud CLI). It runs as a non-root user +`eloq` with passwordless `sudo`. + +Pull it (Docker automatically selects `amd64` or `arm64` for your machine): + +```sh +docker pull eloqdata/ubuntu-dev:24.04 +``` + +Tags: `latest`, `24.04`, and `sha-`. + +Build locally: + +```sh +docker build -t eloqdata/ubuntu-dev ./ubuntu-dev +``` + +## CI + +`.github/workflows/ubuntu-dev.yml` builds the image on native `amd64` and `arm64` runners: + +- **Pull requests** build both architectures for validation (no login, no push). +- **Pushes to `main`** build and push each architecture, then merge them into a single multi-arch + manifest published to DockerHub. + +### Required repository secrets + +| Secret | Value | +| --- | --- | +| `DOCKER_USERNAME` | DockerHub username with write access to `eloqdata/ubuntu-dev` | +| `DOCKER_PASSWORD` | That account's DockerHub password (or access token) | + +Configure them in **Settings → Secrets and variables → Actions**. They are encrypted at rest and +automatically masked in build logs. diff --git a/ubuntu-dev/Dockerfile b/ubuntu-dev/Dockerfile new file mode 100644 index 0000000..7611b1f --- /dev/null +++ b/ubuntu-dev/Dockerfile @@ -0,0 +1,43 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV PATH=/home/eloq/.local/bin:$PATH + +# Force IPv4 for apt to avoid flaky IPv6 mirrors. +RUN echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99force-ipv4 + +# Development toolchain and libraries — everything here is installable via apt. +RUN apt-get update && apt-get install -y --no-install-recommends \ + jq sudo vim wget curl apt-utils \ + python3 python3-dev python3-pip python3-venv \ + gdb lcov ccache rsync \ + build-essential gcc g++ make cmake ninja-build m4 bison pkg-config patchelf \ + git openssh-client openssh-server \ + openjdk-11-jdk golang nodejs npm \ + libprotobuf-dev protobuf-compiler \ + libcurl4-openssl-dev libssl-dev openssl gnutls-dev \ + libncurses5-dev ncurses-dev libreadline-dev \ + zlib1g-dev libbz2-dev liblz4-dev libzstd-dev liblzma-dev \ + libuv1-dev libgflags-dev libleveldb-dev libsnappy-dev libjsoncpp-dev \ + libboost-context-dev libboost-stacktrace-dev \ + libc-ares-dev libc-ares2 libsqlite3-dev libffi-dev \ + ca-certificates tar redis tcl \ + && rm -rf /var/lib/apt/lists/* + +# Google Cloud CLI (installed from Google's apt repository). +RUN curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \ + | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \ + > /etc/apt/sources.list.d/google-cloud-sdk.list \ + && apt-get update && apt-get install -y --no-install-recommends google-cloud-cli \ + && rm -rf /var/lib/apt/lists/* + +# Non-root user with passwordless sudo (no password is baked into the image). +RUN useradd -rm -s /bin/bash -g sudo eloq \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && ln -sf /usr/bin/python3 /usr/bin/python \ + && mkdir -p /var/crash && chown -R eloq /var/crash + +USER eloq +WORKDIR /home/eloq +RUN mkdir -p /home/eloq/workspace From 7935388e598ae21c0e3f8b75250845f11bbd277b Mon Sep 17 00:00:00 2001 From: Jerry Zhao Date: Tue, 30 Jun 2026 15:43:34 +0000 Subject: [PATCH 2/2] Install gnupg so the Google Cloud apt key step works The gcloud repo setup calls `gpg --dearmor`, but gpg is not present in the base ubuntu:24.04 image, causing exit 127. Add gnupg (and apt-transport-https) to the apt install list. Co-Authored-By: Claude Opus 4.8 (1M context) --- ubuntu-dev/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ubuntu-dev/Dockerfile b/ubuntu-dev/Dockerfile index 7611b1f..69d4512 100644 --- a/ubuntu-dev/Dockerfile +++ b/ubuntu-dev/Dockerfile @@ -8,7 +8,7 @@ RUN echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99force-ipv4 # Development toolchain and libraries — everything here is installable via apt. RUN apt-get update && apt-get install -y --no-install-recommends \ - jq sudo vim wget curl apt-utils \ + jq sudo vim wget curl gnupg apt-transport-https apt-utils \ python3 python3-dev python3-pip python3-venv \ gdb lcov ccache rsync \ build-essential gcc g++ make cmake ninja-build m4 bison pkg-config patchelf \