Fix “context deadline exceeded” in Docker and fly deploy
Explore with AI
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.
On this page
- What “context deadline exceeded” is telling you
- Find the step that timed out from the text before it
- Cause 1: the WireGuard tunnel to Fly’s private network can’t get through
- Cause 2: your peer is pinned to a gateway that has gone down
- Cause 3: the remote builder is stuck or the build service is having trouble
- Cause 4: building locally and your own Docker daemon is not answering
- Keeping deploys from timing out again
- Common questions
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 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 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:
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:
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 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 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 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:
fly doctor
fly doctor -o your-org -v
According to the fly doctor reference, 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:
LOG_LEVEL=debug fly deploy
Fix it
-
Update flyctl. Fly’s troubleshooting guide warns that outdated versions cause odd failures.
fly version update -
Move the tunnel to TCP 443 with fly wireguard websockets:
fly wireguard websockets enable -
Run
fly doctoragain. The WireGuard check should pass now. -
Deploy again with
fly deploy. To send builder traffic over plain HTTPS for this deploy, add--wg=false:fly deploy --wg=false -
If it still times out, reset the WireGuard peer for your organisation. The fly wireguard commands include
list,removeandreset: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
-
Check status.flyio.net for an active incident.
-
Reset the peer so flyctl registers a fresh one:
fly wireguard reset fly doctor -
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
-
Use the legacy remote builder. The build still runs remotely but skips Depot:
fly deploy --depot=false -
Build on your own machine. This needs Docker installed locally. The upload is slower, but nothing depends on remote build infrastructure:
fly deploy --local-only -
Take the build out of the deploy completely. Build and push the image wherever you like, then deploy it. The builders reference describes the image option, which skips the build step:
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
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:
$ 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 recommend setting it in daemon.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:
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:
#!/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 fiSave
deploy.logas a CI artifact. The prefix beforecontext deadline exceededtells 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 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=falseor--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.
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
- Troubleshoot your deployment, Fly Docs
- fly deploy, Fly Docs
- fly doctor, Fly Docs
- fly wireguard, Fly Docs
- fly wireguard websockets, Fly Docs
- Builders and Fly.io, Fly Docs
- add support for token-mode wireguard in flyctl agent, superfly/flyctl pull request 5232
- Configure remote access for Docker daemon, Docker Docs
- Daemon proxy configuration, Docker Docs
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
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
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
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
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”
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.