diff --git a/apps/docs/docs/advanced/environment-variables/index.mdx b/apps/docs/docs/advanced/environment-variables/index.mdx
index ff53193e4..2c1d3e22c 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. 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` | - |
+| Environment Variable | Description | Possible values | Default |
+| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------- |
+| `DOCKER_SOCKET_PATHS` | Comma-separated mounted Docker-compatible socket paths. Homarr discovers each path as a selectable Docker environment | For example `/var/run/docker.sock,/var/run/podman.sock` | - |
+| `DOCKER_HOSTNAMES` | Comma-separated plaintext TCP hostnames. Pair each hostname with the port in the same position in `DOCKER_PORTS` | For example `localhost,docker.example.com` | - |
+| `DOCKER_PORTS` | Comma-separated plaintext TCP ports paired with `DOCKER_HOSTNAMES` | For example `2375,2376` | - |
+| `DOCKER_ENDPOINTS` | Optional advanced JSON configuration for custom names, TLS, or restricted capabilities. It takes precedence over the simple variables above. See the [Docker and Podman connection guide](/docs/integrations/docker) | A JSON array of endpoint descriptors | - |
## Database
diff --git a/apps/docs/docs/integrations/docker/index.mdx b/apps/docs/docs/integrations/docker/index.mdx
index f547ded1d..27ab9e422 100644
--- a/apps/docs/docs/integrations/docker/index.mdx
+++ b/apps/docs/docs/integrations/docker/index.mdx
@@ -24,12 +24,11 @@ import { dockerContainersWidget } from "@site/docs/widgets/docker-containers";
]}
/>
-## Enabling the Docker integration
+## Connecting Docker to Homarr
-This integration must first be enabled.
-If you directly run on the host system and you have docker installed, it will be enabled by default.
-Otherwise, you'll need to mount the [docker socket](https://docs.docker.com/engine/security/protect-access/) to the Homarr container,
-so Homarr can interact with the Docker service.
+Docker connections come directly from Homarr's environment variables. They are automatically available in the Docker management table and Docker widget; you do not create a Docker integration under **Management → Integrations**.
+
+If Homarr runs directly on a host with Docker installed, it uses `/var/run/docker.sock` by default. When Homarr runs in a container, mount the [Docker socket](https://docs.docker.com/engine/security/protect-access/) into it so Homarr can interact with Docker.
In Docker Compose, you can simply add this line:
@@ -122,7 +121,23 @@ services:
DOCKER_SOCKET_PATHS: /var/run/docker.sock,/var/run/podman.sock
```
-For named endpoints, transport security, or least-privilege actions, prefer `DOCKER_ENDPOINTS`. It is a JSON array where every endpoint declares its identity, transport, and capabilities:
+For remote socket proxies, list the hostnames and matching ports in the same order:
+
+```yaml
+environment:
+ DOCKER_HOSTNAMES: docker-proxy-home,docker-proxy-lab
+ DOCKER_PORTS: 2375,2375
+```
+
+Homarr automatically discovers each comma-separated entry as a separate Docker environment. All environments are selected by default; the Docker widget lets you select one or more without any additional setup.
+
+:::warning[Plaintext TCP]
+Plaintext Docker TCP can grant unauthenticated control of the Docker host. Prefer a local socket, a least-privilege socket proxy on a private Docker network, or TLS.
+:::
+
+### Advanced endpoint configuration
+
+Most users should use the comma-separated variables above. Use `DOCKER_ENDPOINTS` only when you need custom names, TLS, or restricted capabilities. It is a JSON array where every endpoint declares its identity, transport, and capabilities:
```yaml
services:
@@ -163,36 +178,7 @@ Supported transports are:
- `tls`: `host`, `port`, and an absolute `caPath`; add both `certPath` and `keyPath` for mutual TLS
- `tcp`: `host`, `port`, and the explicit acknowledgement `allowInsecure: true`
-:::warning[Plaintext TCP]
-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.
+The structured configuration refuses TCP unless `allowInsecure` is explicitly set to `true`. When `DOCKER_ENDPOINTS` is set, it takes precedence over `DOCKER_SOCKET_PATHS`, `DOCKER_HOSTNAMES`, and `DOCKER_PORTS`.
### Managing your Docker containers within Homarr
diff --git a/apps/docs/docs/widgets/docker-containers/index.mdx b/apps/docs/docs/widgets/docker-containers/index.mdx
index bd0e2ee19..e405f270d 100644
--- a/apps/docs/docs/widgets/docker-containers/index.mdx
+++ b/apps/docs/docs/widgets/docker-containers/index.mdx
@@ -7,20 +7,15 @@ hide_title: true
import { WidgetHeader } from "@site/src/components/widgets/header";
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
-import { WidgetIntegrations } from "@site/src/components/widgets/integrations";
import { WidgetInteractionGuide } from "@site/src/components/widgets/interactions";
-import { IconAd } from "@tabler/icons-react";
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
import { dockerContainersWidget } from ".";
-import { dockerIntegration } from "@site/docs/integrations/docker";
This widget displays the resource usage of your Docker containers and allows you to manage them.
:::info
-This widget can only be used with administrator privileges.
+This widget reads the Docker environments configured through environment variables. You do not need to create a Docker integration under **Management → Integrations**. The widget can only be used with administrator privileges.
:::
### Screenshots
@@ -29,16 +24,6 @@ import dockerTable from "./img/table.png";
-### Supported Integrations
-
-
-
### Interactions
Hover a column header to reveal its grab handle, then drag it to reorder the table or drag the header's edge to resize it. The layout is saved automatically for each widget instance and restored after a reload. These controls are disabled while the board is in edit mode.
diff --git a/apps/docs/docs/widgets/docker-containers/index.ts b/apps/docs/docs/widgets/docker-containers/index.ts
index 0517938d3..b81514511 100644
--- a/apps/docs/docs/widgets/docker-containers/index.ts
+++ b/apps/docs/docs/widgets/docker-containers/index.ts
@@ -8,6 +8,12 @@ export const dockerContainersWidget: WidgetDefinition = {
path: "../../widgets/docker-containers",
configuration: {
items: [
+ {
+ name: "Docker environments",
+ description: "Select one or more automatically discovered Docker environments, or leave empty to show all",
+ values: "Any Docker socket or host configured for Homarr",
+ defaultValue: "All Docker environments",
+ },
{
name: "Columns to show",
description: "Select which columns are visible in the table",
diff --git a/packages/api/src/router/docker/docker-router.ts b/packages/api/src/router/docker/docker-router.ts
index 1e4934cd2..910d16131 100644
--- a/packages/api/src/router/docker/docker-router.ts
+++ b/packages/api/src/router/docker/docker-router.ts
@@ -23,6 +23,11 @@ const dockerContainerTargetSchema = z.object({
});
const dockerContainerTargetsSchema = z.array(dockerContainerTargetSchema).min(1).max(100);
+const normalizeGetContainersInput = (input: { endpointIds?: string[] } | undefined) => {
+ if (!input?.endpointIds || input.endpointIds.length === 0) return {};
+ return input;
+};
+
export const dockerRouter = createTRPCRouter({
reconcileServices: permissionRequiredProcedure
.requiresPermission("admin")
@@ -72,7 +77,7 @@ export const dockerRouter = createTRPCRouter({
.concat(dockerMiddleware())
.input(z.object({ endpointIds: z.array(z.string().min(1)).max(100).optional() }).optional())
.query(async ({ input }) => {
- const innerHandler = dockerContainersRequestHandler.handler(input ?? {});
+ const innerHandler = dockerContainersRequestHandler.handler(normalizeGetContainersInput(input));
const result = await innerHandler.getDataAsync();
const { data, timestamp } = result;
diff --git a/packages/request-handler/src/docker.ts b/packages/request-handler/src/docker.ts
index 437906a59..430d08ec0 100644
--- a/packages/request-handler/src/docker.ts
+++ b/packages/request-handler/src/docker.ts
@@ -195,9 +195,24 @@ const mockContainers: {
},
];
+const demoDockerEndpoint = {
+ id: "demo",
+ name: "Demo Docker",
+ status: "available",
+ kind: "docker",
+ transport: "socket",
+ capabilities: ["inventory"],
+ source: "default",
+ scope: "admin",
+} satisfies DockerEndpointStatus;
+
export const dockerContainersRequestHandler = createWidgetRequestHandler({
async requestAsync({ endpointIds }: { endpointIds?: string[] }) {
if (isDemoMode()) {
+ const includesDemoEndpoint =
+ endpointIds === undefined || endpointIds.length === 0 || endpointIds.includes("demo");
+ if (!includesDemoEndpoint) return { containers: [], endpoints: [] };
+
return {
containers: mockContainers.map((container) => ({
...container,
@@ -205,18 +220,7 @@ export const dockerContainersRequestHandler = createWidgetRequestHandler({
endpointName: "Demo Docker",
resourceId: `demo:${container.id}`,
})),
- endpoints: [
- {
- id: "demo",
- name: "Demo Docker",
- status: "available",
- kind: "docker",
- transport: "socket",
- capabilities: ["inventory"],
- source: "default",
- scope: "admin",
- },
- ] satisfies DockerEndpointStatus[],
+ endpoints: [demoDockerEndpoint],
};
}
return await getContainersWithStatsAsync(dockerWidgetEndpointTimeoutMs, endpointIds);
@@ -304,6 +308,8 @@ export const streamContainerLogsAsync = async (
};
export const getDockerEndpointsAsync = (): DockerEndpointStatus[] => {
+ if (isDemoMode()) return [demoDockerEndpoint];
+
const dockerInstances = DockerSingleton.getInstances();
const initializationFailures = DockerSingleton.getInitializationFailures();
diff --git a/packages/widgets/src/docker/component.tsx b/packages/widgets/src/docker/component.tsx
index 1b06aafd4..f10f40666 100644
--- a/packages/widgets/src/docker/component.tsx
+++ b/packages/widgets/src/docker/component.tsx
@@ -74,6 +74,11 @@ const createContainerLogsPath = (container: Pick ({ endpointId, id });
+const getContainersQueryInput = (endpointIds: string[]) => {
+ if (endpointIds.length === 0) return undefined;
+ return { endpointIds };
+};
+
const ContainerStateBadge = ({ state }: { state: ContainerState }) => {
const t = useScopedI18n("docker.field.state.option");
@@ -248,7 +253,7 @@ export default function DockerWidget({
const isAdvanced = displayMode === "advanced";
const utils = clientApi.useUtils();
- const containersQuery = clientApi.docker.getContainers.useQuery({ endpointIds: options.endpointIds });
+ const containersQuery = clientApi.docker.getContainers.useQuery(getContainersQueryInput(options.endpointIds));
const data = getUsableWidgetQueryData(containersQuery);
const { isFetching } = containersQuery;
const refreshInventory = clientApi.docker.refreshInventory.useMutation({