jcrist: LGTM, merging.
diff --git a/distributed/utils.py b/distributed/utils.py
index 9f14d58d..b7f6631c 100644
--- a/distributed/utils.py
+++ b/distributed/utils.py
@@ -1434,7 +1434,9 @@ def format_dashboard_link(host, port):
         scheme = "https"
     else:
         scheme = "http"
-    return template.format(scheme=scheme, host=host, port=port, **os.environ)
+    return template.format(
+        **toolz.merge(os.environ, dict(scheme=scheme, host=host, port=port))
+    )
 
 
 def is_coroutine_function(f):

dashboard_link conflicts with environment variables (schema, host, and port)
I incidentally had an environment variable named `host` which prevented a the `cluster.dashboard_link` from rendering. I'm not suggesting a change, just making the error and workaround available in case someone else encounters it.

## Error
```
Traceback (most recent call last):
  File "start_cluster.py", line 4, in <module>
    print(f"dashboard: {cluster.dashboard_link}")
  File "/home/andy/.pyenv/versions/3.7.4/lib/python3.7/site-packages/distributed/deploy/cluster.py", line 201, in dashboard_link
    return format_dashboard_link(host, port)
  File "/home/andy/.pyenv/versions/3.7.4/lib/python3.7/site-packages/distributed/utils.py", line 1437, in format_dashboard_link
    return template.format(scheme=scheme, host=host, port=port, **os.environ)
TypeError: format() got multiple values for keyword argument 'host'
```

## Reproducing
### Script: start_cluster.py
```
from distributed import Client, LocalCluster
if __name__ == '__main__':
    cluster = LocalCluster()
    print(f"dashboard: {cluster.dashboard_link}")
```
### Terminal
```
% export host=xyz
% python start_cluster.py 
```

## Cause
The trouble is that the [format call](https://github.com/dask/distributed/blob/cf26e1a559e2c89c4a4b14b6622111eaf0954f12/distributed/utils.py#L1437) specifies a `host=` and the `**os.environ` also includes a `host=` (from the outside environment). These two conflict. Similar conflicts would happen if you had lower case schema= or port= in your outer environment.

## The workaround
Remove the conflicting variables from the environment
```
unset host
unset port
unset schema
python start_cluster.py
```



diff --git a/distributed/tests/test_utils.py b/distributed/tests/test_utils.py
index bf2d8456..ff733a1a 100644
--- a/distributed/tests/test_utils.py
+++ b/distributed/tests/test_utils.py
@@ -2,6 +2,7 @@ import array
 import datetime
 from functools import partial
 import io
+import os
 import queue
 import socket
 import sys
@@ -40,6 +41,7 @@ from distributed.utils import (
     parse_bytes,
     parse_timedelta,
     warn_on_duration,
+    format_dashboard_link,
 )
 from distributed.utils_test import loop, loop_in_thread  # noqa: F401
 from distributed.utils_test import div, has_ipv6, inc, throws, gen_test, captured_logger
@@ -582,3 +584,17 @@ def test_is_valid_xml():
     assert is_valid_xml("<a>foo</a>")
     with pytest.raises(Exception):
         assert is_valid_xml("<a>foo")
+
+
+def test_format_dashboard_link():
+    with dask.config.set({"distributed.dashboard.link": "foo"}):
+        assert format_dashboard_link("host", 1234) == "foo"
+
+    assert "host" in format_dashboard_link("host", 1234)
+    assert "1234" in format_dashboard_link("host", 1234)
+
+    try:
+        os.environ["host"] = "hello"
+        assert "hello" not in format_dashboard_link("host", 1234)
+    finally:
+        del os.environ["host"]

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
3.6
requirements.txt dev-requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aiohttp==3.8.6
aiosignal==1.2.0
anyio==3.6.2
argon2-cffi==21.3.0
argon2-cffi-bindings==21.2.0
async-generator==1.10
async-timeout==4.0.2
asynctest==0.13.0
attrs==22.2.0
backcall==0.2.0
bleach==4.1.0
bokeh==2.3.3
certifi==2021.5.30
cffi==1.15.1
charset-normalizer==2.0.12
click==8.0.4
cloudpickle==2.2.1
contextvars==2.4
dask==2021.3.0
dataclasses==0.8
decorator==5.1.1
defusedxml==0.7.1
-e git+https://github.com/dask/distributed.git@1ef4f70dc7f8fe048d11104ee8daad04b82227e9#egg=distributed
entrypoints==0.4
frozenlist==1.2.0
HeapDict==1.0.1
idna==3.10
idna-ssl==1.1.0
immutables==0.19
importlib-metadata==4.8.3
iniconfig==1.1.1
ipykernel==5.5.6
ipython==7.16.3
ipython-genutils==0.2.0
jedi==0.17.2
Jinja2==3.0.3
joblib==1.1.1
jsonschema==3.2.0
jupyter-client==7.1.2
jupyter-core==4.9.2
jupyter-server==1.13.1
jupyter-server-proxy==3.2.4
jupyterlab-pygments==0.1.2
MarkupSafe==2.0.1
mistune==0.8.4
mock==5.2.0
msgpack==1.0.5
multidict==5.2.0
nbclient==0.5.9
nbconvert==6.0.7
nbformat==5.1.3
nest-asyncio==1.6.0
numpy==1.19.5
packaging==21.3
pandas==1.1.5
pandocfilters==1.5.1
parso==0.7.1
pexpect==4.9.0
pickleshare==0.7.5
Pillow==8.4.0
pluggy==1.0.0
prometheus-client==0.17.1
prompt-toolkit==3.0.36
psutil==7.0.0
ptyprocess==0.7.0
py==1.11.0
pycparser==2.21
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.1
pyzmq==25.1.2
requests==2.27.1
Send2Trash==1.8.3
simpervisor==0.4
six==1.17.0
sniffio==1.2.0
sortedcontainers==2.4.0
tblib==1.7.0
terminado==0.12.1
testpath==0.6.0
tomli==1.2.3
toolz==0.12.0
tornado==6.1
traitlets==4.3.3
typing_extensions==4.1.1
urllib3==1.26.20
wcwidth==0.2.13
webencodings==0.5.1
websocket-client==1.3.1
yarl==1.7.2
zict==2.1.0
zipp==3.6.0

name: distributed
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aiohttp==3.8.6
      - aiosignal==1.2.0
      - anyio==3.6.2
      - argon2-cffi==21.3.0
      - argon2-cffi-bindings==21.2.0
      - async-generator==1.10
      - async-timeout==4.0.2
      - asynctest==0.13.0
      - attrs==22.2.0
      - backcall==0.2.0
      - bleach==4.1.0
      - bokeh==2.3.3
      - cffi==1.15.1
      - charset-normalizer==2.0.12
      - click==8.0.4
      - cloudpickle==2.2.1
      - contextvars==2.4
      - dask==2021.3.0
      - dataclasses==0.8
      - decorator==5.1.1
      - defusedxml==0.7.1
      - entrypoints==0.4
      - frozenlist==1.2.0
      - heapdict==1.0.1
      - idna==3.10
      - idna-ssl==1.1.0
      - immutables==0.19
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - ipykernel==5.5.6
      - ipython==7.16.3
      - ipython-genutils==0.2.0
      - jedi==0.17.2
      - jinja2==3.0.3
      - joblib==1.1.1
      - jsonschema==3.2.0
      - jupyter-client==7.1.2
      - jupyter-core==4.9.2
      - jupyter-server==1.13.1
      - jupyter-server-proxy==3.2.4
      - jupyterlab-pygments==0.1.2
      - markupsafe==2.0.1
      - mistune==0.8.4
      - mock==5.2.0
      - msgpack==1.0.5
      - multidict==5.2.0
      - nbclient==0.5.9
      - nbconvert==6.0.7
      - nbformat==5.1.3
      - nest-asyncio==1.6.0
      - numpy==1.19.5
      - packaging==21.3
      - pandas==1.1.5
      - pandocfilters==1.5.1
      - parso==0.7.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pillow==8.4.0
      - pluggy==1.0.0
      - prometheus-client==0.17.1
      - prompt-toolkit==3.0.36
      - psutil==7.0.0
      - ptyprocess==0.7.0
      - py==1.11.0
      - pycparser==2.21
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.1
      - pyzmq==25.1.2
      - requests==2.27.1
      - send2trash==1.8.3
      - simpervisor==0.4
      - six==1.17.0
      - sniffio==1.2.0
      - sortedcontainers==2.4.0
      - tblib==1.7.0
      - terminado==0.12.1
      - testpath==0.6.0
      - tomli==1.2.3
      - toolz==0.12.0
      - tornado==6.1
      - traitlets==4.3.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - wcwidth==0.2.13
      - webencodings==0.5.1
      - websocket-client==1.3.1
      - yarl==1.7.2
      - zict==2.1.0
      - zipp==3.6.0
prefix: /opt/conda/envs/distributed

distributed/tests/test_utils.py::test_format_dashboard_link
distributed/tests/test_utils.py::test_All distributed/tests/test_utils.py::test_sync_error distributed/tests/test_utils.py::test_sync_timeout distributed/tests/test_utils.py::test_sync_closed_loop distributed/tests/test_utils.py::test_is_kernel distributed/tests/test_utils.py::test_ensure_ip distributed/tests/test_utils.py::test_get_ip_interface distributed/tests/test_utils.py::test_truncate_exception distributed/tests/test_utils.py::test_get_traceback distributed/tests/test_utils.py::test_str_graph distributed/tests/test_utils.py::test_maybe_complex distributed/tests/test_utils.py::test_read_block distributed/tests/test_utils.py::test_seek_delimiter_endline distributed/tests/test_utils.py::test_funcname distributed/tests/test_utils.py::test_ensure_bytes distributed/tests/test_utils.py::test_ensure_bytes_ndarray distributed/tests/test_utils.py::test_nbytes distributed/tests/test_utils.py::test_open_port distributed/tests/test_utils.py::test_set_thread_state distributed/tests/test_utils.py::test_loop_runner distributed/tests/test_utils.py::test_two_loop_runners distributed/tests/test_utils.py::test_loop_runner_gen distributed/tests/test_utils.py::test_parse_bytes distributed/tests/test_utils.py::test_parse_timedelta distributed/tests/test_utils.py::test_all_exceptions_logging distributed/tests/test_utils.py::test_warn_on_duration distributed/tests/test_utils.py::test_format_bytes_compat distributed/tests/test_utils.py::test_logs distributed/tests/test_utils.py::test_is_valid_xml
distributed/utils.py
distributed/utils.py