MagicTools
Developer ToolsAugust 5, 20266 views4 min read

GitHub Actions Fails Quickly with Connection Reset: The Cross-Border Pitfall When Overseas Runners Pull Alibaba Cloud ACR

1. Problem Description

A routine push triggered a Docker image build, and GitHub Actions failed directly about 2 seconds after starting:

ERROR: failed to authorize: failed to fetch oauth token:
Post "https://dockerauth.cn-hangzhou.aliyuncs.com/auth": read: connection reset by peer

The log error points to this line in the Dockerfile:

FROM registry.cn-hangzhou.aliyuncs.com/<namespace>/node:22-pm2

The first instinct might go astray: is the base image tag wrong? Has the ACR password expired? Is there a problem with the recently changed Dockerfile?

None of these. The only criterion: with the same Dockerfile and credentials, a previous build succeeded, then one failed, with no related changes in between. The code hasn't changed but the result has, so the problem isn't in the code.

2. Environment

Item Details
CI GitHub Actions, GitHub-hosted runner (overseas data center)
Build docker/build-push-action, multi-platform (amd64 + arm64)
Image Repository Alibaba Cloud ACR (Hangzhou region)
Base Image Custom node:22-pm2, also stored in the same ACR

3. Root Cause

Breaking down the link:

  1. GitHub-hosted runner is overseas.
  2. FROM a private ACR image, buildx needs to fetch an OAuth token from dockerauth.cn-hangzhou.aliyuncs.com during the "load metadata" phase.
  3. This overseas → Alibaba Cloud Hangzhou cross-border TCP link has unstable quality, occasionally being directly RST by intermediate devices—manifesting as connection reset by peer.

So this is an intermittent network-layer failure, not an authentication configuration issue (authentication errors return 401/403 HTTP responses, not TCP connection resets), and certainly not a Dockerfile issue (the error points to the FROM line only because the failure occurs during the metadata parsing stage of that line).

A Diagnostic Mantra Worth Remembering

First, check at which second the failure occurs. Second-level failure = network / authentication link; minute-level failure might be compilation / dependency issue.

Failing in 2 seconds, dependencies haven't even started installing; spending time checking npm packages or code is counterproductive.

4. Fix: Add Automatic Retry in Workflow

The intermittent RST of cross-border links cannot be eliminated from our side, but its characteristic is that retries are likely to succeed. Modify the build step:

- name: Build and push
  id: build
  continue-on-error: true        # 第一次失败不终止 job
  uses: docker/build-push-action@v6
  with:
    context: .
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ${{ env.IMAGE_TAGS }}

- name: Wait before retry
  if: steps.build.outcome == 'failure'
  run: sleep 30                  # 给链路一点恢复时间

- name: Build and push (retry)
  if: steps.build.outcome == 'failure'
  uses: docker/build-push-action@v6
  with:                          # ⚠️ 参数必须与上面逐字一致
    context: .
    platforms: linux/amd64,linux/arm64
    push: true
    tags: ${{ env.IMAGE_TAGS }}

Two practical notes:

  • GitHub Actions YAML does not support anchors (&anchor / *ref), so the with blocks of the two steps cannot be reused; they must be manually kept in verbatim sync—when changing parameters later, both places need to be changed, and it's best to leave comments in the file as reminders.

  • continue-on-error: true will make the step status show a green checkmark when the first step fails (the outer job does not fail), so rely on steps.build.outcome to determine the actual result; don't be fooled by the interface color.

After deploying this retry mechanism, similar intermittent resets are caught by the second build, and there have been no more occurrences of the entire pipeline turning red.

5. Structural Solution (Optional)

Retry is just a stopgap. To make the cross-border dependency of "overseas runners pulling domestic images" disappear entirely, the idea is to mirror the base image to an overseas registry that is stably accessible:

  1. Sync and push a copy of the custom base image to GHCR (ghcr.io)
  2. Change the FROM in Dockerfile to GHCR—pulling side no longer crosses borders
  3. Build artifacts are pushed to ACR as before (push-side cross-border writes are tested to be more stable than authentication pulls, and failures are caught by the retries above)

The cost is maintaining an extra image sync. If the intermittent frequency is not high, the retry solution is sufficient; implement this when the failure frequency affects the release rhythm.

6. Troubleshooting Checklist (Go Through in Order for Similar Errors)

  1. At which second does the failure occur? Second-level → network/authentication direction; minute-level → compilation/dependency direction
  2. Is the error at the TCP layer (connection reset / timeout) or HTTP layer (401/403/404)? The former checks the link, the latter checks credentials
  3. Has the same configuration succeeded before? If it succeeded before and there are no related changes → intermittent environmental issue, retry first
  4. The code line pointed to by the error (e.g., FROM) is just the position being processed at the time of failure, not necessarily an issue with that line

Related Articles

Node.js Built-in Fetch Doesn't Use HTTPS_PROXY: The Second Pitfall When Connecting to Google API

curl works with proxy, but fetch in Node scripts times out? Node.js built-in fetch (undici) is designed not to read environment variables like HTTPS_PROXY. This article explains this behavior through the actual troubleshooting process of Google Search Console API, and provides three fix paths: google-auth-library's client.request(), undici ProxyAgent, and the experimental switch in newer Node versions.

developerAug 5, 20264 min
7

GA4 Data API Proxy Environment Timeout DEADLINE_EXCEEDED: gRPC Doesn't Recognize Uppercase HTTPS_PROXY

GA4 Data API times out with DEADLINE_EXCEEDED after 60 seconds in a proxy environment, while curl works fine with the same proxy. The root cause is that the SDK uses gRPC under the hood, and grpc-js only reads lowercase grpc_proxy / https_proxy, making uppercase HTTPS_PROXY invisible to it. This article goes through the error phenomenon to source code layer by layer, providing a paired fix.

developerAug 5, 20264 min
6

Cloudflare Wallets Explained: An Identity and a Stablecoin Wallet for AI Agents — cloudflare.pay Handles Are Live

On August 4, 2026, Cloudflare announced Cloudflare Wallets and cloudflare.pay during Agents Week: a human-readable, stable identity for AI agents plus a stablecoin wallet with owner-defined spending guardrails. This guide covers the problem it solves, how Account Wallets and Virtual Wallets split responsibilities, how x402 micropayments complete the two-sided agentic market, and the one thing you can do today — reserve your wallet handle.

cloudflareai-agent+4
ai-tutorialsAug 5, 20266 min
4

Next.js Full-Site Hydration Mismatch: The Root Cause is GTM-Loaded AdSense, Not Your Code

The Next.js site console randomly shows 'A tree hydrated but some attributes didn't match,' and the first page in cold cache is clean while subsequent pages always report it? The root cause is not in your components, but in the AdSense auto-ads loaded by GTM injecting scripts into the head before React hydration, colliding with the inline GTM script. This article explains the timing puzzle, a one-line fix, and diagnostic techniques using Playwright to capture the full error diff.

developerAug 5, 20264 min
8

Published by MagicTools