`docker pull` hangs behind a corporate proxy though HTTP_PROXY is exported in your shell - where does the proxy setting belong?
answer
- Ask which process makes the outbound call
- The CLI is only a client of the daemon
- dockerd is a service with its own environment
- Drop-in file, or the daemon.json proxies block
- Build steps and containers each need their own
basics
~20 sThe docker CLI only talks to the daemon; dockerd makes the registry connection and never sees your shell environment. Configure the proxy on the daemon itself - a systemd drop-in, or the proxies block in /etc/docker/daemon.json - then restart it.
solid answer
~40 sProxy settings have to be applied to whichever process makes the outbound call, and there are three different ones. **The daemon** pulls and pushes images, so it needs its own environment: a systemd drop-in at `/etc/systemd/system/docker.service.d/http-proxy.conf` setting `Environment="HTTP_PROXY=..."`, or the `proxies` object in `/etc/docker/daemon.json` on Engine 23.0 and later, followed by `systemctl daemon-reload` and a daemon restart. **Build steps** run in their own containers and inherit neither your shell nor the daemon's environment, so `RUN` commands need the predefined proxy build args (`--build-arg HTTP_PROXY=...`). **Running containers** get only the environment you give them with `-e`, unless a `proxies` stanza in `~/.docker/config.json` makes the CLI inject it. Whatever you set, put the internal registry and localhost in `no-proxy`, and confirm with `docker info`, which prints the daemon's proxy values.
code
bash · 10 linessudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf >/dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://proxy.corp.example.com:3128"
Environment="HTTPS_PROXY=http://proxy.corp.example.com:3128"
Environment="NO_PROXY=localhost,127.0.0.1,registry.internal.example.com,.internal.example.com"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
docker info | grep -i proxygo deeper
Remember the split: the docker command you type is only a client, and the daemon does the downloading. Setting a variable in your terminal cannot reach a service that systemd started at boot.
Be able to name the three places proxy settings live - the daemon's environment or daemon.json, the build's proxy build args, and a container's own environment - and say which failure each one explains.
Show that you diagnose before editing: read whether the pull hangs, reports proxyconnect, or returns 407, confirm what the daemon actually holds with docker info, and get no-proxy right so internal registries are not routed outward.
Treat proxy and exclusion lists as fleet configuration, not host edits: they belong in the image or configuration management, must be consistent across build agents and remote engines, and should never carry credentials that a docker inspect can read.
## Why the shell variable does nothing `docker` is a client. Typing `docker pull registry.internal.example.com:8443/search/doc-indexer:1.4.2` marshals a request onto `/var/run/docker.sock`; the daemon on the other end is the process that resolves the registry name, opens the TCP connection and speaks HTTP to it. `dockerd` is normally started by systemd at boot with the environment systemd gives it, which does not include anything you exported in an interactive shell an hour later. So the variable is real, it is simply set in the wrong process. Recognising the symptom helps: a pull that sits silently until it times out usually means the connection is being attempted directly and dropped by an egress firewall; `proxyconnect tcp: dial tcp ...` means the daemon did try a proxy and could not reach it; `407 Proxy Authentication Required` means it reached one and was refused. ## Layer 1 - the daemon's own egress Two supported mechanisms, both applied on the host running dockerd. **systemd drop-in** (works on every version): # /etc/systemd/system/docker.service.d/http-proxy.conf [Service] Environment="HTTP_PROXY=http://proxy.corp.example.com:3128" Environment="HTTPS_PROXY=http://proxy.corp.example.com:3128" Environment="NO_PROXY=localhost,127.0.0.1,registry.internal.example.com,.internal.example.com" then `sudo systemctl daemon-reload && sudo systemctl restart docker`. **daemon.json** on Docker Engine 23.0 and later, using the `proxies` object with the kebab-case keys `http-proxy`, `https-proxy` and `no-proxy`; restart the daemon afterwards. Note the key style differs from the CLI's own configuration file, which uses `httpProxy` / `httpsProxy` / `noProxy` - copying one into the other is a common wasted afternoon. Verify rather than assume: `docker info` prints `HTTP Proxy`, `HTTPS Proxy` and `No Proxy` for the running daemon, and `systemctl show --property=Environment docker` shows what systemd actually handed it. ## Layer 2 - the build A `RUN` step executes in a container the builder creates. It inherits neither your shell nor the daemon's environment; its environment comes from the base image, `ENV` instructions and build arguments. That is why `apt-get`, `sbt` or a package download inside a build can still hang after the daemon is fixed. Docker predefines the proxy build arguments - `HTTP_PROXY`, `HTTPS_PROXY`, `FTP_PROXY`, `NO_PROXY`, `ALL_PROXY` and their lowercase spellings - so you can pass them with `--build-arg` without declaring `ARG` in the Dockerfile, and they are excluded from `docker history`. Setting a `proxies` stanza in `~/.docker/config.json` makes the CLI supply them for you on every build. Two cautions: proxy credentials embedded in the URL end up in the build environment, and the base-image pull at `FROM` is done by the builder itself, so it obeys layer 1, not the build args. ## Layer 3 - running containers A container's environment is the image's `ENV` plus whatever you pass. It does not inherit the daemon's proxy variables. For a container that must reach the internet through the proxy you pass `-e HTTP_PROXY=... -e NO_PROXY=...`, or rely on the same `~/.docker/config.json` `proxies` stanza, which makes the CLI inject those variables into containers it creates. Remember that injected values are visible in `docker inspect`, so a proxy URL carrying a username and password is now readable by anyone who can query the daemon. { "proxies": { "default": { "httpProxy": "http://proxy.corp.example.com:3128", "httpsProxy": "http://proxy.corp.example.com:3128", "noProxy": "localhost,127.0.0.1,registry.internal.example.com" } } } Also remember that an application inside the container only honours these variables if its own HTTP client reads them - a JVM, for instance, needs its own proxy system properties rather than the shell convention. ## Getting no-proxy right The exclusion list is where a working proxy configuration goes wrong next. `no-proxy` accepts comma-separated host names, domain suffixes written with a leading dot, and CIDR blocks, and it is matched against the host in the URL - not against the address that host resolves to. So an internal registry reached by IP must be listed by IP, and a registry reached by name must be listed by name. Omitting the internal registry sends internal pulls out through a proxy that has no route back in; omitting `localhost` breaks anything talking to a service on the host. Every host in the fleet needs the same list, which is an argument for pushing it through configuration management rather than editing files by hand. ## The order to work in Fix the daemon first and prove it with a `docker pull` of a public image. Then fix the build and prove it with a one-line Dockerfile that fetches something. Then fix the container. Diagnosing all three at once is how a two-minute problem becomes an afternoon.
- You give the daemon a proxy and now pulls from the internal registry fail. What is missing?The internal registry is not in `no-proxy`, so its pulls are being sent to a proxy that has no route to it. Add the registry host - and any internal domain suffix, localhost and 127.0.0.1 - to the exclusion list and restart the daemon. Matching is on the host string in the URL, so a registry addressed by IP has to be excluded by IP, not by the name it also answers to.
- A `RUN apt-get update` still times out although the daemon now has a working proxy. Why?Build steps run in their own containers and inherit neither your shell nor the daemon's environment; their environment is the base image plus `ENV` and build arguments. Pass the predefined proxy build args, which need no `ARG` declaration in the Dockerfile, or configure a `proxies` stanza in `~/.docker/config.json` so the CLI supplies them for every build.
- How do you check which proxy the running daemon actually picked up?`docker info` prints `HTTP Proxy`, `HTTPS Proxy` and `No Proxy` as the daemon sees them, which is the fastest confirmation that your file was read. `systemctl show --property=Environment docker` shows the values systemd handed the unit, and the daemon's own journal shows whether it restarted at all after the edit - a forgotten `systemctl daemon-reload` is a common reason nothing changed.
saying these in an interview costs you the question
- Exports HTTP_PROXY in a shell and expects dockerd to use it
- Thinks the docker CLI opens the registry connection
- Edits daemon.json and never restarts the daemon
- Leaves the internal registry out of no-proxy
- Assumes containers inherit the daemon's proxy variables
- Puts proxy credentials in a URL exposed by docker inspect