skip to content

What did Docker's legacy `docker run --link` flag do between two containers, and what replaced it?

level: middleimportance: nice to knowfreq 30%

answer

  1. Pre-network way to find a peer
  2. Two effects on the recipient container
  3. One of them leaks the source's environment
  4. Declared at create time, one direction only
  5. On a user-defined network it is only an alias

basics

~20 s

--link wired one container to another on the default bridge: Docker added an /etc/hosts entry for the chosen alias and injected environment variables describing the source container's exposed ports and its own environment. User-defined networks with embedded name resolution replaced it.

solid answer

~40 s

`docker run --link source:alias` was the pre-network way to let one container find another on the default bridge, where there is no name resolution. Docker wrote an `/etc/hosts` entry mapping `alias` to the source container's address, and injected a block of environment variables into the recipient: `ALIAS_PORT`, `ALIAS_PORT_5432_TCP_ADDR`, `ALIAS_PORT_5432_TCP_PORT` and, importantly, `ALIAS_ENV_*` copies of every environment variable the source container was started with — including its passwords. Links are one-directional, must be declared when the recipient is created, and therefore impose a start order. They are documented as legacy and may eventually be removed. The replacement is a network you create: attach both containers and address the peer by its container name or `--network-alias`. On a user-defined network `--link` no longer injects environment variables; it only adds a network-scoped alias.

code

bash · 9 lines
bash
# Legacy: link declared when the recipient is created
docker run -d --name digest-db -e POSTGRES_PASSWORD=secret postgres:16
docker run --rm --link digest-db:db ruby:3.3-slim-bookworm \
  sh -c 'grep db /etc/hosts; env | grep ^DB_'

# Replacement: both containers on a network you create
docker network create digest-net
docker run -d --name digest-db2 --network digest-net -e POSTGRES_PASSWORD=secret postgres:16
docker run --rm --network digest-net ruby:3.3-slim-bookworm getent hosts digest-db2

go deeper

for a junior

Recognise --link as something you will see in old tutorials and scripts, and know the one-line replacement: create a network, attach both containers, use the container name. You are not expected to recall the injected variable names.

for a middle

Explain both halves of what the flag did — the /etc/hosts alias and the injected ALIAS_PORT_* and ALIAS_ENV_* variables — and why those variables go stale when the source container restarts with a new address.

for a senior

Treat it as a migration and a security question: spot the credential leak through ALIAS_ENV_*, and be able to convert a legacy script full of links into one network plus --network flags without changing application behaviour.

for a principal

Use it as the example of why implicit cross-container configuration sharing was removed: dependencies should be explicit and resolvable at call time, not copied into an environment at start time and never revoked.

## The problem --link was invented for On Docker's default bridge there is no name resolution between containers. A container gets an address from the bridge's subnet when it starts, and that address can be different next time. Before user-defined networks existed, the only way to give one container a stable handle on another was `--link`, and every tutorial written in that era uses it. ## What the flag actually did The syntax is `docker run --link <source-container>:<alias>`, given when the **recipient** container is created. Docker then did two things to the recipient: **An /etc/hosts entry.** The alias (and the source container's name) were written into the recipient's `/etc/hosts`, pointing at the source's current address, so `getent hosts db` or a connection to `db:5432` worked. Docker maintained that entry: if the source container restarted with a new address, the linked container's hosts file was updated. **Environment variables.** For each port the source container exposed, the recipient received a set of variables built from the alias in upper case — for a Postgres source linked as `db` you would see `DB_PORT=tcp://172.17.0.3:5432`, `DB_PORT_5432_TCP_ADDR`, `DB_PORT_5432_TCP_PORT`, `DB_PORT_5432_TCP_PROTO` and `DB_NAME`. On top of that, every environment variable set on the source container was copied in as `DB_ENV_<NAME>`. That is the part interviewers like, because `DB_ENV_POSTGRES_PASSWORD` means the source's credentials are now sitting in the environment of a container that may have no business seeing them, visible to anything that can read `/proc/self/environ` or run `docker inspect`. Those variables were fixed when the recipient started. If the source restarted and took a new address, the hosts entry was corrected but the variables kept the stale one — a classic source of "it works until the database is restarted" bugs in an application that read `DB_PORT_5432_TCP_ADDR` instead of the hostname. ## The structural problems * **One-directional.** Linking a worker to a database gives the worker a handle on the database, not the reverse. * **Create-time only.** A link cannot be added to a running container, and cannot be removed without recreating it. * **Start order.** The source must already exist when the recipient is created, so a stack has a hand-maintained dependency graph. * **Default bridge only, in the sense that mattered.** It solved a problem that only exists there. ## What replaced it Create a network and attach both containers. Docker's embedded resolver answers for container names and for any `--network-alias`, resolution is current rather than baked in at start, it works in both directions, containers can be attached and detached later, and nothing is copied between environments: ``` docker network create digest-net docker run -d --name digest-db --network digest-net -e POSTGRES_PASSWORD=secret postgres:16 docker run -d --name digest-worker --network digest-net digest:1.4 # connects to digest-db:5432 ``` ## The subtlety worth knowing `--link` is still accepted by the CLI, and it behaves differently depending on where you use it. On the default bridge it does the legacy thing described above. On a **user-defined** network it does not inject environment variables at all; it simply gives the source container an extra network-scoped alias inside that network, so `--link digest-db:db` means "also let me call it `db` here". That is the only piece of `--link` that has a modern use, and even then a `--network-alias` on the source is the clearer way to express it. ## How to answer when asked Say what it did (hosts entry plus environment variables), name the environment-variable leak as the reason nobody should reintroduce it, name the structural limits (one-directional, create-time, start order), and say the replacement is a user-defined network with name resolution. If you are shown a legacy script full of `--link`, the migration is mechanical: create one network, add `--network` to every `docker run`, delete the `--link` flags, and replace any application code that reads `*_PORT_*_TCP_ADDR` with the peer's name. ## Why it still comes up Because old documentation and old CI scripts are full of it, and because the environment-variable behaviour is a neat illustration of why implicit configuration sharing between containers is a bad idea. Nobody is marked down for not remembering the exact variable names; being unable to say what replaced it, or claiming it is the current way to connect containers, is a different matter.

  • Why is the environment-variable half of --link a security concern?
    Because it copies every environment variable of the source container into the recipient as `ALIAS_ENV_*`. Link a container to a database started with `POSTGRES_PASSWORD`, and that password is now in the linked container's environment, readable by any process in it and by anyone who can run `docker inspect`. Nothing about the recipient asked for the credential, and nothing revokes it.
  • What happens to a linked container when the source container restarts with a different address?
    Docker keeps the recipient's `/etc/hosts` entry up to date, so code that connects to the alias keeps working. The injected environment variables are not updated — they were set when the recipient started and still hold the old address. An application that read `DB_PORT_5432_TCP_ADDR` instead of the hostname connects to nothing.
  • Does --link still do anything if both containers are on a network you created?
    Yes, but something much smaller: no environment variables are injected, and the flag simply adds a network-scoped alias for the source container inside that network. A `--network-alias` on the source expresses the same thing more clearly, so there is no reason to reach for `--link` in new work.

saying these in an interview costs you the question

  • Presents --link as the current way to connect containers
  • Thinks a link works in both directions
  • Believes --link injects environment variables on any network
  • Says a link can be added to an already running container
  • Cannot name the user-defined network as the replacement
  • Ignores that linked env vars carry the source's secrets

context