diff --git a/services/coder/README.md b/services/coder/README.md index 32657888..5e360859 100644 --- a/services/coder/README.md +++ b/services/coder/README.md @@ -31,7 +31,7 @@ This stack runs Coder with a Tailscale sidecar, as described in [the standard se ## Deviations from the standard setup -- **Extra container.** The stack runs a `database` container with PostgreSQL. It uses the network of the `tailscale` container as well, so Coder reaches it at `localhost`. PostgreSQL therefore also listens on port `5432` of the Tailscale IP address of the device. +- **Extra container.** The stack runs a `database` container with PostgreSQL. It uses the network of the `tailscale` container as well, so Coder reaches it at `localhost`. PostgreSQL listens only on the loopback address of the device, so other devices on your Tailnet cannot reach it. - **Docker socket.** Coder mounts `/var/run/docker.sock` read-only, so that templates can use Docker on the host. The `:ro` flag only makes the socket file read-only. It does not limit what the service can do through the Docker API, so treat access to the socket as root access to the Docker host. - **Image version.** `CODER_VERSION` in `.env` selects the version of the Coder image. @@ -43,6 +43,8 @@ Open the web interface and create the first account, which becomes the administr Earlier versions of this stack had a sample value for `POSTGRES_PASSWORD` in `.env`. It is now empty, and Compose stops with an error until you set it. If you already run the stack, keep the values that you use now. This is required for the database password, because the database applies it only at the first start. If you kept the sample value, set `POSTGRES_PASSWORD=strongpassword` again. The database still uses it. +Earlier versions listened on all addresses of the device, so PostgreSQL was reachable from your Tailnet. This version makes it listen on localhost only. Your data stays in place. Run `docker compose up -d` to recreate the `database` container. Any tool that connects to the database from another device stops working. + ## Links - [Coder documentation](https://coder.com/docs) diff --git a/services/coder/compose.yaml b/services/coder/compose.yaml index e4ce92d2..bf345696 100644 --- a/services/coder/compose.yaml +++ b/services/coder/compose.yaml @@ -79,6 +79,7 @@ services: # Minimum supported version is 13. # More versions here: https://hub.docker.com/_/postgres image: "postgres:17" + command: ["postgres", "-c", "listen_addresses=localhost"] # Listen on localhost only, so the Tailnet cannot reach the database environment: POSTGRES_USER: ${POSTGRES_USER:-username} # The PostgreSQL user (useful to connect to the database) POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} # The PostgreSQL password (useful to connect to the database) diff --git a/services/docmost/README.md b/services/docmost/README.md index 8b3be56a..3b98bb60 100644 --- a/services/docmost/README.md +++ b/services/docmost/README.md @@ -26,7 +26,7 @@ Set these values in `.env`. Compose stops with an error if one of them is empty. ## Deviations from the standard setup -- **Extra containers.** The stack runs `db` (PostgreSQL) and `redis`. Both use the network of the `tailscale` container as well, so Docmost reaches them at `localhost`. PostgreSQL and Redis therefore also listen on ports `5432` and `6379` of the Tailscale IP address of the device. +- **Extra containers.** The stack runs `db` (PostgreSQL) and `redis`. Both use the network of the `tailscale` container as well, so Docmost reaches them at `localhost`. PostgreSQL and Redis listen only on the loopback address of the device, so other devices on your Tailnet cannot reach them. - **Application address.** `APP_URL` in `compose.yaml` is `http://localhost:3000`. Docmost uses this value for the links that it generates, for example in emails. Change it to `https://docmost..ts.net` if you use such links. ## First run @@ -37,6 +37,8 @@ Open the web interface. Docmost shows its setup page, where you create your work If your `compose.yaml` contained the secret and the database password before, set `APP_SECRET` and `DB_PASSWORD` in `.env` to those same values. +Earlier versions listened on all addresses of the device, so PostgreSQL and Redis were reachable from your Tailnet. This version makes them listen on localhost only. Your data stays in place. Run `docker compose up -d` to recreate the `db` and `redis` containers. Any tool that connects to the database or to Redis from another device stops working. + ## Links - [Docmost documentation](https://docmost.com/docs/) diff --git a/services/docmost/compose.yaml b/services/docmost/compose.yaml index 6f1ada5a..f87f8f47 100644 --- a/services/docmost/compose.yaml +++ b/services/docmost/compose.yaml @@ -79,6 +79,7 @@ services: image: postgres:16-alpine network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale container_name: app-${SERVICE}-database # Name for local container management + command: ["postgres", "-c", "listen_addresses=localhost"] # Listen on localhost only, so the Tailnet cannot reach the database environment: POSTGRES_DB: docmost POSTGRES_USER: docmost @@ -97,6 +98,7 @@ services: image: redis:7.2-alpine network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale container_name: app-${SERVICE}-redis # Name for local container management + command: ["redis-server", "--bind", "127.0.0.1", "-::1"] # Listen on localhost only, so the Tailnet cannot reach Redis healthcheck: test: ["CMD", "redis-cli", "ping"] # Check if Redis responds interval: 10s # How often to perform the check diff --git a/services/kaneo/README.md b/services/kaneo/README.md index 4ba3a161..e63eb058 100644 --- a/services/kaneo/README.md +++ b/services/kaneo/README.md @@ -23,7 +23,7 @@ Set these values in `.env`: ## Deviations from the standard setup -- **Extra container.** The stack runs a `postgres` container. It uses the network of the `tailscale` container as well, so Kaneo reaches it at `localhost`. PostgreSQL therefore also listens on port `5432` of the Tailscale IP address of the device. +- **Extra container.** The stack runs a `postgres` container. It uses the network of the `tailscale` container as well, so Kaneo reaches it at `localhost`. PostgreSQL listens only on the loopback address of the device, so other devices on your Tailnet cannot reach it. - **Serve port from `.env`.** The Tailscale Serve configuration in `compose.yaml` takes its port from `SERVICEPORT`. - **Database image.** `IMAGE_URL_DATABASE` in `.env` selects the PostgreSQL image. - **The containers read the whole `.env` file.** Both containers load `.env` through `env_file`. Every variable in that file, including `TS_AUTHKEY`, is therefore present in their environment. @@ -38,6 +38,8 @@ Since [release v2.7.0](https://github.com/usekaneo/kaneo/releases/tag/v2.7.0), K When you update from such a version, start the stack with `docker compose up -d --remove-orphans`. Compose then removes the old `frontend` and `backend` containers, which use the same ports as the new container. +Earlier versions listened on all addresses of the device, so PostgreSQL was reachable from your Tailnet. This version makes it listen on localhost only. Your data stays in place. Run `docker compose up -d` to recreate the `postgres` container. Any tool that connects to the database from another device stops working. + ## Links - [Kaneo documentation](https://kaneo.app/docs) diff --git a/services/kaneo/compose.yaml b/services/kaneo/compose.yaml index b788f526..8039ad8c 100644 --- a/services/kaneo/compose.yaml +++ b/services/kaneo/compose.yaml @@ -53,6 +53,7 @@ services: image: ${IMAGE_URL_DATABASE} # Image to be used network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale container_name: app-${SERVICE}-postgres # Name for local container management + command: ["postgres", "-c", "listen_addresses=localhost"] # Listen on localhost only, so the Tailnet cannot reach the database env_file: - .env environment: diff --git a/services/miniflux/README.md b/services/miniflux/README.md index 8480950f..dbf57610 100644 --- a/services/miniflux/README.md +++ b/services/miniflux/README.md @@ -24,13 +24,17 @@ Set these values in `.env`: ## Deviations from the standard setup -- **Extra container.** The stack runs a `db` container with PostgreSQL. It uses the network of the `tailscale` container as well, so Miniflux reaches it at `localhost`. PostgreSQL therefore also listens on port `5432` of the Tailscale IP address of the device. +- **Extra container.** The stack runs a `db` container with PostgreSQL. It uses the network of the `tailscale` container as well, so Miniflux reaches it at `localhost`. PostgreSQL listens only on the loopback address of the device, so other devices on your Tailnet cannot reach it. - **Automatic setup.** `RUN_MIGRATIONS=1` and `CREATE_ADMIN=1` make Miniflux prepare the database and create the administrator at the start. ## First run Open the web interface and log in with the administrator account from `.env`. +## Upgrading + +Earlier versions listened on all addresses of the device, so PostgreSQL was reachable from your Tailnet. This version makes it listen on localhost only. Your data stays in place. Run `docker compose up -d` to recreate the `db` container. Any tool that connects to the database from another device stops working. + ## Links - [Miniflux documentation](https://miniflux.app/docs/) diff --git a/services/miniflux/compose.yaml b/services/miniflux/compose.yaml index b424c487..9e750e8f 100644 --- a/services/miniflux/compose.yaml +++ b/services/miniflux/compose.yaml @@ -78,6 +78,7 @@ services: image: postgres:15-alpine network_mode: service:tailscale # Join the same network namespace to be accessible via localhost container_name: app-${SERVICE}-db + command: ["postgres", "-c", "listen_addresses=localhost"] # Listen on localhost only, so the Tailnet cannot reach the database environment: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} diff --git a/services/pocket-id/.env b/services/pocket-id/.env index 19a8e103..2b44064d 100644 --- a/services/pocket-id/.env +++ b/services/pocket-id/.env @@ -26,10 +26,10 @@ APP_URL=https://pocket-id..ts.net # Encryption key (choose one method): # Method 1: Direct key (simple but less secure) -# Generate with: openssl rand -base64 32 +# Required: encrypts the stored data, such as the token signing keys. Generate it with: openssl rand -base64 32 ENCRYPTION_KEY= # Method 2: File-based key (recommended) -# Put the base64 key in a file and point to it here. +# Put the key in a file and point to it here. The file wins over ENCRYPTION_KEY, but Compose still requires ENCRYPTION_KEY to be set. # ENCRYPTION_KEY_FILE=/path/to/encryption_key # These variables are optional but recommended to review: diff --git a/services/pocket-id/README.md b/services/pocket-id/README.md index b437144c..54469594 100644 --- a/services/pocket-id/README.md +++ b/services/pocket-id/README.md @@ -17,7 +17,7 @@ This stack runs Pocket ID with a Tailscale sidecar, as described in [the standar - **Enable HTTPS certificates.** HTTPS certificates must be [enabled for your Tailnet](https://console.tailscale.com/admin/dns) (**DNS** > **HTTPS Certificates**). Passkeys only work over HTTPS. - **Set `APP_URL` in `.env`.** Use the address of the web interface, `https://pocket-id..ts.net`. Pocket ID uses it for its OIDC issuer, its endpoints, and passkeys, and it does not start with the sample value. -- **Set `ENCRYPTION_KEY` in `.env`.** Generate the key with `openssl rand -base64 32`. +- **Set `ENCRYPTION_KEY` in `.env`.** Generate the key with `openssl rand -base64 32`. Compose stops with an error if it is empty. ## Deviations from the standard setup @@ -37,6 +37,10 @@ Open `https://pocket-id..ts.net/setup` to create the administrator acco - **Custom domains.** Tailscale Serve only serves the `ts.net` name of the device. A custom domain in `APP_URL` needs your own DNS and reverse proxy, which this stack does not include. - **Local network access.** The `ports` block stays commented out. If you enable it, the stack publishes plain HTTP on the Docker host, where passkeys do not work. +## Upgrading + +Earlier versions of this stack had an empty `ENCRYPTION_KEY` in `.env`, and Compose started the stack without an error. Pocket ID then stopped at start. Compose now stops with an error until you set `ENCRYPTION_KEY`. If you already run the stack with a key, keep the value that you use now, because Pocket ID encrypts its stored data with it. If you use `ENCRYPTION_KEY_FILE`, set `ENCRYPTION_KEY` as well, because Compose still requires it. + ## Links - [Pocket ID installation](https://pocket-id.org/docs/setup/installation) diff --git a/services/pocket-id/compose.yaml b/services/pocket-id/compose.yaml index 06dde482..682bc9cd 100644 --- a/services/pocket-id/compose.yaml +++ b/services/pocket-id/compose.yaml @@ -53,6 +53,8 @@ services: network_mode: service:tailscale # Sidecar configuration to route the service through Tailscale container_name: app-${SERVICE} # Name for local container management env_file: .env + environment: + - ENCRYPTION_KEY=${ENCRYPTION_KEY:?Set ENCRYPTION_KEY in .env} volumes: - ./${SERVICE}-data:/app/data depends_on: diff --git a/services/tandoor/README.md b/services/tandoor/README.md index e92a2368..3db30cbb 100644 --- a/services/tandoor/README.md +++ b/services/tandoor/README.md @@ -26,7 +26,7 @@ Set these values in `.env`: ## Deviations from the standard setup -- **Extra container.** The stack runs a `database` container with PostgreSQL. It uses the network of the `tailscale` container as well, so Tandoor reaches it at `127.0.0.1`. PostgreSQL therefore also listens on port `5432` of the Tailscale IP address of the device. +- **Extra container.** The stack runs a `database` container with PostgreSQL. It uses the network of the `tailscale` container as well, so Tandoor reaches it at `127.0.0.1`. PostgreSQL listens only on the loopback address of the device, so other devices on your Tailnet cannot reach it. - **Service port.** `TANDOOR_PORT` makes Tandoor listen on the port from `SERVICEPORT`, which is `9001`. - **The container reads the whole `.env` file.** The `application` container loads `.env` through `env_file`. Every variable in that file, including `TS_AUTHKEY`, is therefore present in its environment. @@ -38,6 +38,8 @@ The first start can take a few minutes, because Tandoor prepares its database. T Earlier versions of this stack had sample values for `SECRET_KEY` and `POSTGRES_PASSWORD` in `.env`. They are now empty, and Compose stops with an error until you set them. If you already run the stack, keep the values that you use now. This is required for the database password, because the database applies it only at the first start. If you kept the sample value, set `POSTGRES_PASSWORD=REPLACE_WITH_RANDOM_ALPHANUMERIC_PASSWORD` again. The database still uses it. +Earlier versions listened on all addresses of the device, so PostgreSQL was reachable from your Tailnet. This version makes it listen on localhost only. Your data stays in place. Run `docker compose up -d` to recreate the `database` container. Any tool that connects to the database from another device stops working. + ## Links - [Tandoor Recipes documentation](https://docs.tandoor.dev/) diff --git a/services/tandoor/compose.yaml b/services/tandoor/compose.yaml index f6f37417..9053af51 100644 --- a/services/tandoor/compose.yaml +++ b/services/tandoor/compose.yaml @@ -78,6 +78,7 @@ services: image: postgres:16-alpine network_mode: service:tailscale container_name: app-${SERVICE}-database + command: ["postgres", "-c", "listen_addresses=localhost"] # Listen on localhost only, so the Tailnet cannot reach the database environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} POSTGRES_USER: ${POSTGRES_USER} diff --git a/services/traefik/README.md b/services/traefik/README.md index 63bb405b..f995b249 100644 --- a/services/traefik/README.md +++ b/services/traefik/README.md @@ -22,22 +22,46 @@ Nothing beyond the [Quick Start](../../README.md#quick-start). - **Published host port.** The `ports` block is active and publishes port `80` of the Docker host. Devices in your local network can therefore reach Traefik without Tailscale. - **Service name.** The application service is called `traefik_proxy`, not `application`. -- **Docker socket.** Traefik mounts `/var/run/docker.sock` to discover containers and their labels. +- **Docker socket.** Traefik mounts `/var/run/docker.sock` with write access to discover containers and their labels. Treat access to the socket as root access to the Docker host. - **Configuration through flags.** The `command` block in `compose.yaml` is the static configuration. Traefik ignores these flags when it finds a static configuration file, so edit the flags and do not add a `traefik.yml` file. - **Sample site.** The stack runs a `simpleweb` container with routing labels as an example. Replace it with your own services. - **Only port 80.** Tailscale Serve listens on port `443` of the Tailnet address and forwards to the `web` entrypoint of Traefik on port `80`. Do not add a Traefik entrypoint on port `443`. Traefik shares the network of the `tailscale` container, where that port is in use, so Traefik would exit and restart in a loop. - **Health check.** The health check calls the ping endpoint, so keep the `--ping=true` flag. Traefik only routes to containers that Docker reports as healthy, so the sample site is reachable only after its first health check passes. +- **Dashboard.** The stack does not serve the Traefik dashboard or its API. Serving them needs a router with a password, which is described in [Configuration](#configuration). ## First run -Requests through your Tailnet arrive with the host name `traefik..ts.net`. The sample routers match `traefik.domain.local` and `simpleweb.domain.local`, so Traefik answers `404` over the Tailnet at first. +Requests through your Tailnet arrive with the host name `traefik..ts.net`. The sample router matches `simpleweb.domain.local`, so Traefik answers `404` over the Tailnet at first. -Change a `Host()` rule in the labels in `compose.yaml` to `traefik..ts.net` and restart the stack. That router is then reachable at `https://traefik..ts.net`. +Change the `Host()` rule of the `simpleweb` router in the labels in `compose.yaml` to `traefik..ts.net` and restart the stack. The sample site is then reachable at `https://traefik..ts.net`. + +## Configuration + +**Dashboard.** Add a router that uses the `api@internal` service, and protect it with a basic-auth middleware. Create the password entry with `htpasswd -nB `, and write each `$` of the hash as `$$`, because Compose reads `$` in labels as a variable. Add these labels to the `traefik_proxy` service in `compose.yaml`: + +```yaml + labels: + - traefik.enable=true + - traefik.http.routers.dashboard.rule=Host(`traefik..ts.net`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`)) + - traefik.http.routers.dashboard.entrypoints=web + - traefik.http.routers.dashboard.service=api@internal + - traefik.http.services.dashboard.loadbalancer.server.port=8080 # Traefik drops the labels of a container without a port - this label gives it one + - traefik.http.routers.dashboard.middlewares=dashboard-auth + - traefik.http.middlewares.dashboard-auth.basicauth.users=: +``` + +Restart the stack, then open `https://traefik..ts.net/dashboard/` and sign in with the user from the hash. Without a router, the dashboard and the API are not served. + +Sign in through the Tailnet address only. The `ports` block publishes port `80` of the Docker host over plain HTTP, so a device on your local network that sends the Tailnet host name to that port reaches the sign-in prompt without TLS. ## Troubleshooting Traefik writes its log to `./traefik-data/log/traefik.log`, so `docker logs` shows nothing for the Traefik container. Read that file when the container restarts or a router does not work. +## Upgrading + +Earlier versions of this stack served the Traefik dashboard and API without a password. They were reachable on port `8080` of the Tailscale IP address, and on port `80` of the Docker host for the host name `traefik.domain.local`. This version serves neither. The dashboard router and the geoblock plugin flags are removed. Start the stack with `docker compose up -d`. No new variable is required. If you used the dashboard, add the router from [Configuration](#configuration). + ## Links - [Traefik documentation](https://doc.traefik.io/traefik/) diff --git a/services/traefik/compose.yaml b/services/traefik/compose.yaml index 4a68d42c..0d56fa04 100644 --- a/services/traefik/compose.yaml +++ b/services/traefik/compose.yaml @@ -70,7 +70,7 @@ services: - ./${SERVICE}-data/log/:/var/log/ - /var/run/docker.sock:/var/run/docker.sock #Required for the Service Connections command: # Static configuration. Traefik ignores these flags if it finds a traefik.yml file. - - "--api.insecure=true" + - "--api.dashboard=true" # Enables the API and dashboard for a router you add; no router serves them by default - see the README - "--ping=true" # Required by the health check - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" @@ -79,13 +79,6 @@ services: - --log.filepath=/var/log/traefik.log - --accesslog=true - --accesslog.filepath=/var/log/traefik.access.log - - --experimental.plugins.traefik-plugin-geoblock.modulename=github.com/nscuro/traefik-plugin-geoblock - - --experimental.plugins.traefik-plugin-geoblock.version=v0.14.0 - labels: - - traefik.enable=true - - traefik.http.routers.mydashboard.rule=Host(`traefik.domain.local`) - - traefik.http.routers.mydashboard.service=api@internal - - traefik.http.services.mydashboard.loadbalancer.server.port=8080 # Traefik drops the labels of a container without a port - this container exposes none, because it uses the Tailscale network simpleweb: image: yeasy/simple-web:latest