From 5fc87ae00ca84c407da0c6e6a9aab80c7c942386 Mon Sep 17 00:00:00 2001 From: "devsy-app[bot]" <277138668+devsy-app[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:13:05 +0000 Subject: [PATCH 1/2] feat(image): add devsy agent image + workflow Adds a customized OpenHands agent-server Dockerfile that pre-installs the full devsy toolchain (Go 1.26.3, task, golangci-lint v2.12.2, goreleaser, ginkgo, protoc + protoc-gen-go(-grpc), Node 24, biome, gh, act, prek, uv, xvfb) so fleet agents do not spend reasoning budget bootstrapping it. Adds a GitHub Actions workflow that builds and pushes the image to ghcr.io/devsy-org/openhands-agent-server on changes to the Dockerfile or workflow, using the workflow GITHUB_TOKEN (packages: write). PR builds validate without pushing; merges to main push latest + sha tags. GitHub Actions cache is enabled. This PR was created by an AI agent (OpenHands) on behalf of skevetter. --- .github/workflows/agent-image.yml | 60 +++++++++++++++++++++++ Dockerfile.openhands-devsy | 80 +++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 .github/workflows/agent-image.yml create mode 100644 Dockerfile.openhands-devsy diff --git a/.github/workflows/agent-image.yml b/.github/workflows/agent-image.yml new file mode 100644 index 000000000..9b0469b6f --- /dev/null +++ b/.github/workflows/agent-image.yml @@ -0,0 +1,60 @@ +name: Agent Image + +on: + push: + branches: [main] + paths: + - "Dockerfile.openhands-devsy" + - ".github/workflows/agent-image.yml" + pull_request: + branches: [main] + paths: + - "Dockerfile.openhands-devsy" + - ".github/workflows/agent-image.yml" + workflow_dispatch: + +concurrency: + group: agent-image-${{ github.ref }} + cancel-in-progress: true + +env: + IMAGE: ghcr.io/${{ github.repository_owner }}/openhands-agent-server + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + + - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 + with: + images: ${{ env.IMAGE }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=sha,format=short + + - name: Build and push + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + file: Dockerfile.openhands-devsy + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile.openhands-devsy b/Dockerfile.openhands-devsy new file mode 100644 index 000000000..646772256 --- /dev/null +++ b/Dockerfile.openhands-devsy @@ -0,0 +1,80 @@ +ARG GO_VERSION=1.26.3 +ARG GOLANGCI_LINT_VERSION=2.12.2 +ARG NODE_MAJOR=24 +ARG GORELEASER_VERSION=v2 +ARG PREK_INSTALLER_URL=https://github.com/j178/prek/releases/latest/download/prek-installer.sh +ARG ACT_INSTALLER_URL=https://raw.githubusercontent.com/nektos/act/master/install.sh + +FROM ghcr.io/openhands/agent-server:1.39.1-python + +ARG GO_VERSION +ARG GOLANGCI_LINT_VERSION +ARG NODE_MAJOR +ARG GORELEASER_VERSION +ARG PREK_INSTALLER_URL +ARG ACT_INSTALLER_URL + +USER root + +RUN apt-get update -qq \ + && apt-get install -y -qq --no-install-recommends \ + protobuf-compiler \ + xvfb \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +ENV GOTOOLCHAIN=auto \ + GOPATH=/go \ + PATH="/usr/local/go/bin:/go/bin:${PATH}" +RUN ARCH="$(dpkg --print-architecture)" \ + && curl -sSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" -o /tmp/go.tgz \ + && rm -rf /usr/local/go \ + && tar -C /usr/local -xzf /tmp/go.tgz \ + && rm /tmp/go.tgz \ + && mkdir -p "${GOPATH}/bin" "${GOPATH}/src" + +RUN go install github.com/go-task/task/v3/cmd/task@latest \ + && go install github.com/onsi/ginkgo/v2/ginkgo@latest \ + && go install "github.com/goreleaser/goreleaser/v2@${GORELEASER_VERSION}" \ + && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.6.2 \ + && go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11 + +RUN ARCH="$(dpkg --print-architecture)" \ + && curl -sSL "https://github.com/golangci/golangci-lint/releases/download/v${GOLANGCI_LINT_VERSION}/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-${ARCH}.tar.gz" -o /tmp/gcl.tgz \ + && tar -C /tmp -xzf /tmp/gcl.tgz \ + && mv "/tmp/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-${ARCH}/golangci-lint" /usr/local/bin/golangci-lint \ + && rm -rf /tmp/gcl.tgz "/tmp/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-${ARCH}" \ + && golangci-lint --version + +RUN curl -sSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ + -o /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && chmod a+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ + > /etc/apt/sources.list.d/github-cli.list \ + && apt-get update -qq \ + && apt-get install -y -qq gh \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -sSL "${ACT_INSTALLER_URL}" | bash -s -- -b /usr/local/bin \ + && act --version 2>/dev/null || true + +RUN curl --proto '=https' --tlsv1.2 -LsSf "${PREK_INSTALLER_URL}" | sh \ + && ln -sf "${HOME}/.local/bin/prek" /usr/local/bin/prek \ + && prek --version + +RUN curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - \ + && apt-get install -y -qq nodejs \ + && rm -rf /var/lib/apt/lists/* \ + && npm install -g @biomejs/biome \ + && node --version && npm --version + +RUN command -v uv >/dev/null 2>&1 \ + || (curl -LsSf https://astral.sh/uv/install.sh | sh \ + && ln -sf "$HOME/.local/bin/uv" /usr/local/bin/uv) \ + && uv --version + +RUN set -e; for t in go task golangci-lint goreleaser ginkgo \ + protoc protoc-gen-go protoc-gen-go-grpc \ + gh act prek node npm npx biome uv uvx git curl xvfb-run; do \ + command -v "$t" >/dev/null 2>&1 || { echo "missing: $t"; exit 1; }; \ + done From 88f93cc67f73fd50941cbcb4eb6e5b9d52ed9138 Mon Sep 17 00:00:00 2001 From: "devsy-app[bot]" <277138668+devsy-app[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:52:23 +0000 Subject: [PATCH 2/2] perf(image): clean go cache, 13.9GB to 6.98GB Clean the Go module source cache (/go/pkg/mod, 3.3GB) and compile cache (/root/.cache/go-build, 1.8GB) after installing the Go tools. These caches only held the dependency graphs of the installed tools (goreleaser, ginkgo, task, protoc plugins) and are not needed to run the compiled binaries. devsy builds do not share those module graphs (only ~1 overlap: onsi/ginkgo), so the cost is a few MB re-download vs a recurring 5.1GB image-size cost on every pull. Image size: 13.9GB -> 6.98GB (-50%). All 20 tools verified functional in the cleaned image. --- Dockerfile.openhands-devsy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile.openhands-devsy b/Dockerfile.openhands-devsy index 646772256..106dd30f7 100644 --- a/Dockerfile.openhands-devsy +++ b/Dockerfile.openhands-devsy @@ -37,7 +37,9 @@ RUN go install github.com/go-task/task/v3/cmd/task@latest \ && go install github.com/onsi/ginkgo/v2/ginkgo@latest \ && go install "github.com/goreleaser/goreleaser/v2@${GORELEASER_VERSION}" \ && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.6.2 \ - && go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11 + && go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11 \ + && go clean -cache \ + && rm -rf /go/pkg/mod /root/.cache/go-build RUN ARCH="$(dpkg --print-architecture)" \ && curl -sSL "https://github.com/golangci/golangci-lint/releases/download/v${GOLANGCI_LINT_VERSION}/golangci-lint-${GOLANGCI_LINT_VERSION}-linux-${ARCH}.tar.gz" -o /tmp/gcl.tgz \