# Fix “context deadline exceeded” in Docker and fly deploy

> What “context deadline exceeded” means in Docker and fly deploy, how to find the call that timed out, and the fixes for WireGuard, builders and daemons.

By Boris Tane, Founder of Polylane · Published October 1, 2026 · 10 min read
Canonical: https://polylane.com/learn/troubleshooting/docker-context-deadline-exceeded/

```text
context deadline exceeded
```

“context deadline exceeded” is Go's standard error for a call that hit its time limit before anything answered. A Docker client gave up waiting, usually on a remote Docker daemon or builder it could not reach. On Fly.io a common cause is fly deploy failing to reach the remote builder over the WireGuard private network, so run fly doctor, switch WireGuard to websockets or reset it, and deploy with --depot=false or --local-only while you fix the network path.

You usually see this at the end of a long error line. `fly deploy` prints `Building image`, sits still for a while, then fails. The words come from Go's standard library, and they mean one thing: a call had a deadline, and the deadline passed before the call finished.

The message on its own never tells you what to fix. The text in front of it names the call that gave up, and that call is what you fix. On Fly.io a common cause is flyctl trying to reach a Docker daemon on a remote builder over Fly's private WireGuard network.

## What “context deadline exceeded” is telling you

In Go, a context carries a deadline down a chain of function calls. The [context package docs](https://pkg.go.dev/context) say a context with a deadline is cancelled once the deadline passes. From then on, `ctx.Err()` returns `context.DeadlineExceeded`, and that error prints as `context deadline exceeded`. Go returns a separate error when code cancels a context on purpose, so this exact text always means a timer ran out.

That tells you three things:

- **The client gave up.** The machine that printed the error stopped waiting. The other end may never have received the request.
- **The deadline passed first.** The error records only that time ran out. Likely reasons are dropped packets, a tunnel that never came up, or a service that is hung.
- **Start with the path.** Look at the network between you and the daemon, or at the daemon itself, before you look at your app.

A failing Dockerfile looks different. Fly's [troubleshooting guide](https://fly.io/docs/getting-started/troubleshooting/) says a build that fails with `exit code: 1` is your Dockerfile, and that `docker build .` reproduces it locally. If you see `context deadline exceeded` after a prefix about connecting to Docker, a likely reading is that the build never got going.

## Find the step that timed out from the text before it

flyctl wraps the Go error in a chain of prefixes. Read the line from the left: each prefix is one layer that failed because the layer under it timed out. Two shapes cover most `fly deploy` failures.

**flyctl could not finish connecting to the builder:**

```text
Error failed to fetch an image or build from source: error connecting to docker: failed building options: failed probing "<app>": context deadline exceeded
```

`error connecting to docker` means flyctl was still setting up its connection to the Docker daemon on the remote builder. You may also see a warning that the remote builder heartbeat failed to start. That warning comes from the same timeout.

**flyctl had an address but the Docker API never answered:**

```text
Error failed to fetch an image or build from source: error fetching docker server info: Get "http://[fdaa:...]:2375/v1.41/info": context deadline exceeded
```

The address starting `fdaa` is on Fly's private network. Port `2375` is Docker's default port for the remote API without TLS (`2376` with TLS), as the [Docker remote access docs](https://docs.docker.com/engine/daemon/remote-access/) describe. flyctl sent a request to the builder's Docker daemon through the tunnel, and no reply came back in time.

Both shapes point at the path between your machine and the builder. The causes below are a suggested order to check them in.

## Cause 1: the WireGuard tunnel to Fly's private network can't get through

Remote builds are reached over Fly's private network, and flyctl joins that network through a WireGuard gateway. The [flyctl pull request that adds token-mode WireGuard](https://github.com/superfly/flyctl/pull/5232) describes the two existing modes:

- **Regular mode:** flyctl talks WireGuard directly to the gateway on UDP port 51820.
- **Websockets mode:** flyctl opens a websocket over TLS on TCP port 443, and the gateway forwards WireGuard packets between the websocket and its own UDP socket.

If your network drops outbound UDP on 51820, regular mode can't bring the tunnel up, and requests to the builder's `fdaa` address can wait until the deadline passes. The [fly deploy reference](https://fly.io/docs/flyctl/deploy/) lists two flags for this path: `--https-failover`, on by default, falls back to plain HTTPS to the remote builder if WireGuard fails, and `--wg`, also true by default, decides whether builder traffic goes over WireGuard or plain HTTPS.

### How to tell it is the tunnel

Run the diagnostics Fly recommends first:

```bash
fly doctor
fly doctor -o your-org -v
```

According to the [fly doctor reference](https://fly.io/docs/flyctl/doctor/), `fly doctor` checks WireGuard, IPs and Docker. `-o` sets the organisation for the WireGuard tests (it defaults to `personal`) and `-v` prints extra detail. If the WireGuard check fails while the rest pass, the tunnel is your problem. For the full sequence during a deploy, run it with debug logging:

```bash
LOG_LEVEL=debug fly deploy
```

### Fix it

1. Update flyctl. Fly's troubleshooting guide warns that outdated versions cause odd failures.

   ```bash
   fly version update
   ```

2. Move the tunnel to TCP 443 with [fly wireguard websockets](https://fly.io/docs/flyctl/wireguard-websockets/):

   ```bash
   fly wireguard websockets enable
   ```

3. Run `fly doctor` again. The WireGuard check should pass now.
4. Deploy again with `fly deploy`. To send builder traffic over plain HTTPS for this deploy, add `--wg=false`:

   ```bash
   fly deploy --wg=false
   ```

5. If it still times out, reset the WireGuard peer for your organisation. The [fly wireguard commands](https://fly.io/docs/flyctl/wireguard/) include `list`, `remove` and `reset`:

   ```bash
   fly wireguard list
   fly wireguard reset
   ```

On office networks, VPNs and CI runners that allow HTTPS out and little else, websockets mode or `--wg=false` is a sensible first thing to try.

## Cause 2: your peer is pinned to a gateway that has gone down

This cause looks like cause 1, except it appears suddenly on a network where deploys worked yesterday. The same flyctl pull request explains why it happens. Both WireGuard modes register a peer through Fly's central API in `iad`. A peer is set up for one specific gateway, and if that gateway goes down, the peer is not moved to another one. Neither the central API nor flyctl knows whether a gateway is healthy.

So a single gateway outage can leave your tunnel pointing at nothing. Your network is fine, and your app is running fine, but every deploy times out.

### Fix it

1. Check [status.flyio.net](https://status.flyio.net) for an active incident.
2. Reset the peer so flyctl registers a fresh one:

   ```bash
   fly wireguard reset
   fly doctor
   ```

3. If you need to ship before the tunnel recovers, build on your own machine (see cause 3).

The token-mode gateway in that pull request was merged on 25 September 2026 and aims at this failure. It runs on Fly's edge servers behind an anycast IP and listens only for websocket connections on 443. It checks your token against a nearby auth replica and sets up the peer on the local edge. When one edge goes down, traffic moves to another. Whether it is in a flyctl release yet is unconfirmed: the pull request notes the production gateway still needed a deploy. Keep flyctl updated.

## Cause 3: the remote builder is stuck or the build service is having trouble

Sometimes the tunnel is fine and the builder is what has stalled. Fly's troubleshooting guide says remote builds use Depot, and that `fly deploy` hangs when Depot has problems. A hang that ends in a timeout prints this same error.

### Fix it, from least to most change

1. Use the legacy remote builder. The build still runs remotely but skips Depot:

   ```bash
   fly deploy --depot=false
   ```

2. Build on your own machine. This needs Docker installed locally. The upload is slower, but nothing depends on remote build infrastructure:

   ```bash
   fly deploy --local-only
   ```

3. Take the build out of the deploy completely. Build and push the image wherever you like, then deploy it. The [builders reference](https://fly.io/docs/reference/builders/) describes the image option, which skips the build step:

   ```bash
   fly deploy --image registry.example.com/my-app:1.4.2
   ```

If `--depot=false` works and plain `fly deploy` doesn't, check the status page before you change anything else. Fly notes that Docker Hub pulls go through its caching proxy. Images on other registries, such as GitHub Container Registry, skip that cache and can hit rate limits. That failure prints `too many requests`, so you can tell it apart from a timeout.

## Cause 4: building locally and your own Docker daemon is not answering

Once you switch to `--local-only`, the far end of the call is your own Docker daemon, and the same timeout can come from there.

### How to tell it is the local daemon

```bash
docker info
docker build .
```

If `docker info` hangs or fails, flyctl can't reach the daemon either. By default the daemon listens on a Unix socket for local clients. If you set it up to listen on TCP, the Docker remote access docs show how to confirm the listener:

```console
$ sudo netstat -lntp | grep dockerd
tcp        0      0 127.0.0.1:2375          0.0.0.0:*               LISTEN      3758/dockerd
```

The same docs warn that a systemd override and a `hosts` entry in `daemon.json` conflict and stop Docker from starting. Use only one of them.

### When the daemon can't pull base images behind a proxy

If the build starts but times out pulling the base image, the daemon may need your company proxy. The [Docker daemon proxy docs](https://docs.docker.com/engine/daemon/proxy/) recommend setting it in `daemon.json`:

```json
{
  "proxies": {
    "http-proxy": "http://proxy.example.com:3128",
    "https-proxy": "http://proxy.example.com:3128",
    "no-proxy": "*.test.example.com,.example.org,127.0.0.0/8"
  }
}
```

Then restart the daemon:

```bash
sudo systemctl restart docker
```

Docker Desktop ignores proxy settings in `daemon.json`. Set them in Docker Desktop's own proxy settings.

## Keeping deploys from timing out again

The causes above repeat, so make the checks part of how you deploy.

- **Run a preflight in CI.** Update flyctl, force websockets mode, and fall back to other builders before you call the deploy failed:

  ```bash
  #!/bin/sh
  # deploy.sh
  set -u
  fly version update
  fly wireguard websockets enable
  fly doctor || echo "fly doctor reported a problem, continuing to deploy" >&2
  if ! LOG_LEVEL=debug fly deploy 2> deploy.log; then
    echo "Remote build failed, retrying with the legacy builder" >&2
    fly deploy --depot=false || fly deploy --local-only
  fi
  ```

  Save `deploy.log` as a CI artifact. The prefix before `context deadline exceeded` tells you which layer failed, and you won't get that information back once the runner is gone.
- **Build images outside the deploy.** If your CI already builds and pushes images, deploy with `--image`. A builder outage then can't block a release.
- **Watch the status page.** Subscribe to [status.flyio.net](https://status.flyio.net) so you hear about a gateway or builder incident before you start resetting your own setup.
- **Track how often the fallback runs.** Count the deploys that needed `--depot=false` or `--local-only`. If that count rises, the remote path is getting worse, and you'll know before it fails outright.

Running on Fly.io? See [how Polylane monitors Fly.io in production](https://polylane.com/for/fly/).

## Common questions

**Is “context deadline exceeded” a Docker bug or a Fly.io bug?**

Usually it is neither. It is Go's standard timeout error, returned when a context's deadline passes before the call finishes. The text before it names the call that timed out. In fly deploy, a common cause is the connection to the remote builder over WireGuard.

**How is this different from a cancelled context?**

Go returns a separate error when code cancels a context on purpose. context.DeadlineExceeded is returned only when the deadline itself passes, so this message always means a timer ran out while flyctl or Docker was waiting for a reply.

**What does fly doctor check?**

It checks WireGuard, IPs and Docker. Pass -o with your organisation name, because the WireGuard tests default to personal. Add -v for extra diagnostic detail or -j for JSON output.

**Which ports does flyctl need open to reach a remote builder?**

Regular WireGuard mode uses UDP port 51820 to the gateway. Websockets mode uses TCP port 443 over TLS. If your network blocks outbound UDP, run fly wireguard websockets enable, or deploy with --wg=false to send builder traffic over plain HTTPS.

**Does fly deploy --local-only need Docker on my machine?**

Yes. It builds with your local Docker, so Docker has to be installed and the daemon running. The upload is slower, but the build doesn't depend on Depot or the remote builder.

**Why does the error mention port 2375?**

2375 is Docker's default port for the remote API without TLS, and 2376 is the default with TLS. In a fly deploy error, it means flyctl sent a request to the Docker daemon on the remote builder through the private network and got no answer before the deadline.

**Can I just raise the timeout?**

The fly deploy reference documents no timeout for the builder connection. Its timeout flags, --wait-timeout and --lease-timeout, cover machines during the rollout. A timeout here usually means the other end never answered: the tunnel is down, the gateway is gone or the builder is stuck. Fix the path with websockets mode, --wg=false, a WireGuard reset or a different builder.

**I reset WireGuard and it still fails. What next?**

Check status.flyio.net for a gateway or builder incident. Then try fly deploy --depot=false, and if that also times out, fly deploy --local-only. If neither works, build and push the image elsewhere and run fly deploy --image.

## Sources

- [context package, Go standard library](https://pkg.go.dev/context)
- [Troubleshoot your deployment, Fly Docs](https://fly.io/docs/getting-started/troubleshooting/)
- [fly deploy, Fly Docs](https://fly.io/docs/flyctl/deploy/)
- [fly doctor, Fly Docs](https://fly.io/docs/flyctl/doctor/)
- [fly wireguard, Fly Docs](https://fly.io/docs/flyctl/wireguard/)
- [fly wireguard websockets, Fly Docs](https://fly.io/docs/flyctl/wireguard-websockets/)
- [Builders and Fly.io, Fly Docs](https://fly.io/docs/reference/builders/)
- [add support for token-mode wireguard in flyctl agent, superfly/flyctl pull request 5232](https://github.com/superfly/flyctl/pull/5232)
- [Configure remote access for Docker daemon, Docker Docs](https://docs.docker.com/engine/daemon/remote-access/)
- [Daemon proxy configuration, Docker Docs](https://docs.docker.com/engine/daemon/proxy/)

## About the author

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

## Related

- [Kubernetes CrashLoopBackOff: causes and fixes](https://polylane.com/learn/troubleshooting/kubernetes-crashloopbackoff/): What CrashLoopBackOff means in Kubernetes, how to read the exit code and events behind it, and the fix for each common cause, from bad config to failing probes.
- [Kubernetes OOMKilled (exit code 137): causes and fixes](https://polylane.com/learn/troubleshooting/kubernetes-oomkilled-exit-code-137/): Why Kubernetes reports OOMKilled with exit code 137, how to tell a limit kill from a node eviction or a plain SIGKILL, and how to size memory so it stops.
- [Fix “error umounting /data: EBUSY” on Fly.io Machines](https://polylane.com/learn/troubleshooting/fly-error-umounting-data-ebusy-device-or-resource-busy-retrying/): Why Fly.io logs “error umounting /data: EBUSY: Device or resource busy” at shutdown, how to find what stopped the Machine, and how to keep it running.
- [Cloudflare Containers agent sandboxes: startup and setup](https://polylane.com/learn/deployment-safety/cloudflare-containers-agent-sandboxes-startup-time/): Cloudflare Containers now start agent sandboxes in a median 648 ms. Set up the durable_object policy, runtime images and snapshots, and know the limits.
- [Fix “Durable Object reset because its code was updated”](https://polylane.com/learn/troubleshooting/cloudflare-durable-object-reset-because-its-code-was-updated/): Why a deploy makes Cloudflare Durable Objects throw “reset because its code was updated”, and how to retry safely, keep clients connected and lose no state.

Get started with one command: `curl -fsSL https://polylane.com/setup | bash` installs the CLI, connects your coding agents, and creates the account.
