diff --git a/apps/docs/docs/advanced/environment-variables/index.mdx b/apps/docs/docs/advanced/environment-variables/index.mdx
index 4048512a1..ff53193e4 100644
--- a/apps/docs/docs/advanced/environment-variables/index.mdx
+++ b/apps/docs/docs/advanced/environment-variables/index.mdx
@@ -58,12 +58,12 @@ A random secret can be generated by using the following command: `openssl rand -
## Docker
-| Environment Variable | Description | Possible values | Default |
-| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------- |
-| `DOCKER_ENDPOINTS` | JSON array of named, capability-scoped Docker or Podman endpoints. Supports socket, explicitly insecure TCP, and TLS transports. See the [Docker and Podman integration guide](/docs/integrations/docker) | A JSON array of endpoint descriptors | - |
-| `DOCKER_HOSTNAMES` | Legacy comma-separated list of plaintext TCP hostnames. Ignored when `DOCKER_ENDPOINTS` is set | For example `localhost,docker.example.com` | - |
-| `DOCKER_PORTS` | Legacy comma-separated list of plaintext TCP ports. Ignored when `DOCKER_ENDPOINTS` is set | For example `2375,2376` | - |
-| `DOCKER_SOCKET_PATHS` | Legacy comma-separated list of mounted Docker-compatible socket paths. Ignored when `DOCKER_ENDPOINTS` is set | For example `/var/run/docker.sock,/var/run/podman.sock` | - |
+| Environment Variable | Description | Possible values | Default |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------- |
+| `DOCKER_ENDPOINTS` | JSON array of named, capability-scoped Docker or Podman endpoints. Each stable `id` can be selected in Docker widgets. Supports socket, explicitly insecure TCP, and TLS transports. See the [Docker and Podman integration guide](/docs/integrations/docker) | A JSON array of endpoint descriptors | - |
+| `DOCKER_HOSTNAMES` | Legacy comma-separated list of plaintext TCP hostnames. Ignored when `DOCKER_ENDPOINTS` is set | For example `localhost,docker.example.com` | - |
+| `DOCKER_PORTS` | Legacy comma-separated list of plaintext TCP ports. Ignored when `DOCKER_ENDPOINTS` is set | For example `2375,2376` | - |
+| `DOCKER_SOCKET_PATHS` | Legacy comma-separated list of mounted Docker-compatible socket paths. Ignored when `DOCKER_ENDPOINTS` is set | For example `/var/run/docker.sock,/var/run/podman.sock` | - |
## Database
diff --git a/apps/docs/docs/integrations/docker/index.mdx b/apps/docs/docs/integrations/docker/index.mdx
index 2b83460dc..f547ded1d 100644
--- a/apps/docs/docs/integrations/docker/index.mdx
+++ b/apps/docs/docs/integrations/docker/index.mdx
@@ -167,12 +167,37 @@ Supported transports are:
Plaintext Docker TCP can grant unauthenticated control of the Docker host. Prefer a local socket, a least-privilege socket proxy, or TLS. The structured configuration refuses TCP unless `allowInsecure` is explicitly set to `true`.
:::
+For example, two socket proxies can be configured as separate environments:
+
+```yaml
+environment:
+ DOCKER_ENDPOINTS: >-
+ [
+ {
+ "id": "home",
+ "name": "Home Docker",
+ "kind": "docker",
+ "transport": { "type": "tcp", "host": "docker-proxy-home", "port": 2375, "allowInsecure": true },
+ "capabilities": ["inventory", "logs", "lifecycle"]
+ },
+ {
+ "id": "lab",
+ "name": "Lab Docker",
+ "kind": "docker",
+ "transport": { "type": "tcp", "host": "docker-proxy-lab", "port": 2375, "allowInsecure": true },
+ "capabilities": ["inventory", "logs"]
+ }
+ ]
+```
+
+Homarr must be attached to the Docker network of each proxy, and every configured host must be reachable from the Homarr container.
+
When `DOCKER_ENDPOINTS` is set, it takes precedence over the legacy `DOCKER_SOCKET_PATHS`, `DOCKER_HOSTNAMES`, and `DOCKER_PORTS` variables.
### Managing your Docker containers within Homarr
Homarr allows you to interact with Docker containers running on your system.
-The **Service setup** section compares the visible containers with your existing Homarr apps and integrations. It keeps every service distinct by Docker endpoint and container ID, including multiple containers that use the same integration type.
+The collapsed **Assisted Docker setup** helper optionally compares visible containers with your existing Homarr apps and integrations. Open it only when you want setup suggestions; it never blocks normal container management. Every service stays distinct by Docker endpoint and container ID, including multiple containers that use the same integration type.
Homarr can show a service as:
@@ -187,7 +212,7 @@ Suggested addresses are ranked and explain where they came from. A published add
Service discovery is read-only. Homarr never creates or changes an app or integration automatically; use the action on an individual suggestion to review and confirm its setup.
:::
-Use the inbox filters to focus on services that need attention or those already represented in Homarr. Refresh runs discovery again. Dismissed suggestions are stored in the current browser using their endpoint-qualified service identity and can be restored from the same panel. If multiple existing items could match, Homarr marks the result as ambiguous and does not choose one automatically.
+Suggestions appear as a compact task list. Expand an item only when you want to inspect its address, match confidence, or observed health layers. Use the filters to focus on services that need attention or those already represented in Homarr. Refresh runs discovery again. Dismissed suggestions are stored in the current browser using their endpoint-qualified service identity and can be restored from the same helper. If multiple existing items could match, Homarr marks the result as ambiguous and does not choose one automatically.
The health badges are an evidence-based projection across Docker, integration configuration, app representation, and persisted widget links. Authentication, API request success, and widget query execution are labeled **Not observed** because Homarr does not currently retain trustworthy last-run results for these layers. Use the suggested integration test or board action to diagnose them live; **Not observed** is never presented as healthy.
@@ -216,6 +241,10 @@ This opens a dedicated page with an xterm.js terminal that streams the container
The Docker management table supports optional **CPU usage** and **Memory usage** columns.
These columns are hidden by default and can be toggled on from the table column visibility settings.
+#### Choosing Docker environments in the widget
+
+The Docker widget can show one or more configured Docker environments. Leave **Docker environments** empty to include every endpoint, which is also the default for existing widgets. Selecting one or more entries limits container and statistics requests to those endpoints.
+
#### Add apps from containers
Homarr also lets you add apps from containers. You can simply select all containers you want to add an app for and then click the `Add to Homarr` button.
diff --git a/apps/docs/docs/management/api/index.mdx b/apps/docs/docs/management/api/index.mdx
index bd2ab1363..44181afe8 100644
--- a/apps/docs/docs/management/api/index.mdx
+++ b/apps/docs/docs/management/api/index.mdx
@@ -110,6 +110,10 @@ Pass those pairs unchanged to `docker.startAll`, `docker.stopAll`, `docker.resta
names use underscores, such as `docker_restartAll`. Endpoint qualification prevents identical container IDs on different
Docker or Podman endpoints from targeting the wrong service.
+`docker.getContainers` also accepts an optional `endpointIds` array. Omit it or pass an empty array to query every configured
+Docker environment, or pass one or more IDs returned by `docker.getEndpoints` to query only those Dockerode clients. The
+Docker widget uses the same behavior for its environment selector.
+
## Partial integration failures
Multi-integration widget queries keep data from integrations that remain available. Calendar and media-server results include
diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-reconciliation.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-reconciliation.tsx
index 55c85523a..c8b9b592b 100644
--- a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-reconciliation.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-reconciliation.tsx
@@ -1,22 +1,34 @@
"use client";
-import { useEffect, useState } from "react";
+import { useEffect, useId, useState } from "react";
import {
+ ActionIcon,
Alert,
Avatar,
Badge,
Button,
- Card,
+ Collapse,
+ Divider,
Group,
+ Paper,
SegmentedControl,
Select,
SimpleGrid,
Stack,
Text,
TextInput,
+ ThemeIcon,
+ Tooltip,
} from "@mantine/core";
import { useLocalStorage } from "@mantine/hooks";
-import { IconAlertTriangle, IconArrowRight, IconEyeOff, IconPlugConnected, IconRefresh } from "@tabler/icons-react";
+import {
+ IconAlertTriangle,
+ IconArrowRight,
+ IconChevronDown,
+ IconEyeOff,
+ IconRefresh,
+ IconSparkles,
+} from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
@@ -39,8 +51,13 @@ type ServiceHealth = RouterOutputs["docker"]["getServiceHealth"]["services"][num
export const DockerReconciliation = () => {
const t = useScopedI18n("docker.reconciliation");
const utils = clientApi.useUtils();
+ const panelId = useId();
+ const [isOpen, setIsOpen] = useLocalStorage({
+ key: "homarr-docker-reconciliation-open",
+ defaultValue: false,
+ });
const reconciliation = clientApi.docker.reconcileServices.useQuery();
- const health = clientApi.docker.getServiceHealth.useQuery();
+ const health = clientApi.docker.getServiceHealth.useQuery(undefined, { enabled: isOpen });
const refreshInventory = clientApi.docker.refreshInventory.useMutation({
async onSuccess() {
await Promise.all([
@@ -79,86 +96,112 @@ export const DockerReconciliation = () => {
const unavailableEndpoints = reconciliation.data.endpoints.filter(({ status }) => status === "unavailable");
const candidates = filterDockerReconciliationInbox(reconciliation.data.candidates, filter, dismissedCandidateKeys);
- const attentionCount = health.data
- ? health.data.services.filter(
- ({ key, status }) =>
- ["newRecognized", "newApp", "moved"].includes(status) && !dismissedCandidateKeys.includes(key),
- ).length
- : 0;
+ const attentionCount = reconciliation.data.candidates.filter(
+ ({ candidateKey, state }) =>
+ ["newRecognized", "newApp", "moved"].includes(state) && !dismissedCandidateKeys.includes(candidateKey),
+ ).length;
const isRefreshing = refreshInventory.isPending || reconciliation.isFetching || health.isFetching;
+ const toggleLabel = isOpen ? t("action.hide") : t("action.review");
return (
-
-
+
+
-