diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b76c21b..2dd7ad95 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,8 @@ repos: exclude: | (?x)^( docker/security/authelia/| - docker/automation/mosquitto/ + docker/automation/mosquitto/| + docker/tools/searxng/config/ ) - id: mixed-line-ending - id: fix-byte-order-marker diff --git a/Taskfile.yaml b/Taskfile.yaml index f0601062..5003cddb 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -87,6 +87,12 @@ tasks: - task: docs:clean - task: azure-vm:clean - task: aws-vm:clean + - task: clean-venvs + + clean-venvs: + desc: Remove Python virtual environments (.venv directories) + cmds: + - find . -type d -name '.venv' -exec rm -rf {} + btrfs-compsize: desc: Show btrfs compression statistics diff --git a/config-example/docker/myhost/.env b/config-example/docker/myhost/.env index 20a3131e..ba4d462a 100644 --- a/config-example/docker/myhost/.env +++ b/config-example/docker/myhost/.env @@ -69,6 +69,10 @@ CROWDSEC_BOUNCER_API_KEY="use-some-very-secure-value-here" # Then create the user on Settings -> Admins UNIFI_LOCAL_VIEWONLY_USERNAME="use-some-very-secure-value-here" UNIFI_LOCAL_VIEWONLY_PASSWORD="use-some-very-secure-value-here" +# Used to connect the shared mongodb instance +UNIFI_MONGO_DBNAME="unifi" +UNIFI_MONGO_USERNAME="use-some-very-secure-value-here" +UNIFI_MONGO_PASSWORD="use-some-very-secure-value-here" # Generate a secure random key (min 32 chars) using: `openssl rand -base64 32` OPEN_WEBUI_SECRET_KEY="use-some-very-secure-value-here" @@ -103,6 +107,8 @@ OBSIDIAN_LIVESYNC_PASSPHRASE="use-some-very-secure-value-here" GITEA_SECRET_KEY="use-some-very-secure-value-here" # Generate these with: docker run --rm gitea/gitea:1.24.6 gitea generate secret INTERNAL_TOKEN GITEA_INTERNAL_TOKEN="use-some-very-secure-value-here" +# act_runner registration - Navigate to Gitea Admin → Actions → Runners → Create Runner +GITEA_RUNNER_REGISTRATION_TOKEN="use-some-very-secure-value-here" ### Microsoft SQL Server configuration # Password requirements: At least 8 characters, with uppercase, lowercase, numbers, and special characters diff --git a/dotfiles/README.md b/config-example/dotfiles/README.md similarity index 100% rename from dotfiles/README.md rename to config-example/dotfiles/README.md diff --git a/docker/ai/litellm/config/config.yaml b/docker/ai/litellm/config/config.yaml index 818fcac3..0ee5a62a 100644 --- a/docker/ai/litellm/config/config.yaml +++ b/docker/ai/litellm/config/config.yaml @@ -87,9 +87,9 @@ model_list: model: gemini/gemini-2.5-pro api_key: "os.environ/GEMINI_API_KEY" - - model_name: gemini-2.5-flash + - model_name: gemini-3.5-flash litellm_params: - model: gemini/gemini-2.5-flash + model: gemini/gemini-3.5-flash api_key: "os.environ/GEMINI_API_KEY" # --- Via Openrouter diff --git a/docker/ai/litellm/litellm.yaml b/docker/ai/litellm/litellm.yaml index e44afdb8..890e779a 100644 --- a/docker/ai/litellm/litellm.yaml +++ b/docker/ai/litellm/litellm.yaml @@ -9,7 +9,7 @@ name: litellm services: litellm: - image: ghcr.io/berriai/litellm:v1.91.0-stable + image: ghcr.io/berriai/litellm:v1.91.0 command: - --config=/app/config.yaml - --telemetry=false diff --git a/docker/ai/ollama/ollama-amdgpu.yaml b/docker/ai/ollama/ollama-amdgpu.yaml index bb08b820..cff3067f 100644 --- a/docker/ai/ollama/ollama-amdgpu.yaml +++ b/docker/ai/ollama/ollama-amdgpu.yaml @@ -1,14 +1,22 @@ -# GPU override for Ollama — AMD ROCm hardware inference. +# GPU override for Ollama — AMD iGPU inference via the Vulkan backend. # Enable by setting GPU_COMPOSE_SUFFIX=amdgpu in config/docker//.env -# Switches to the :rocm image tag and passes both DRI and KFD devices. +# NOTE: deliberately the STANDARD image, not :rocm — colony's Vega iGPU +# (Ryzen 7 5825U, gfx90c) has no rocblas kernels in ROCm builds ("dropping +# ROCm device"), while the standard image ships the ggml Vulkan backend +# (the :rocm image does not), which supports Vega iGPUs via RADV. +# OLLAMA_IGPU_ENABLE opts the iGPU into scheduling on Linux. +# References: +# - Vulkan backend for AMD/Intel GPUs: https://github.com/ollama/ollama/issues/11247 +# - Hardware support matrix (Vulkan path for iGPUs): https://docs.ollama.com/gpu --- name: ollama services: ollama: - image: ollama/ollama:0.31.1-rocm + image: ollama/ollama:0.31.1 devices: - /dev/dri/renderD128:/dev/dri/renderD128 - - /dev/kfd:/dev/kfd + environment: + OLLAMA_IGPU_ENABLE: "1" group_add: - "${GPU_RENDER_GID}" # render group — numeric GID required (name may not exist in container) - "${GPU_VIDEO_GID}" # video group diff --git a/docker/automation/n8n/n8n.yaml b/docker/automation/n8n/n8n.yaml index d78439b3..2b0866ae 100644 --- a/docker/automation/n8n/n8n.yaml +++ b/docker/automation/n8n/n8n.yaml @@ -8,6 +8,13 @@ --- name: n8n services: + n8n-postgres-init: + image: busybox:1.38.0 + container_name: n8n-postgres-init + command: ["sh", "-c", "chown -R 70:70 /var/lib/postgresql/data"] + volumes: + - ${DOCKER_VOLUMES}/n8n/postgres-data:/var/lib/postgresql/data + n8n-postgres: image: postgres:16-alpine container_name: n8n-postgres @@ -19,7 +26,10 @@ services: POSTGRES_PASSWORD: ${N8N_POSTGRES_PASSWORD} POSTGRES_DB: ${N8N_POSTGRES_DB} volumes: - - n8n_postgres_storage:/var/lib/postgresql/data + - ${DOCKER_VOLUMES}/n8n/postgres-data:/var/lib/postgresql/data + depends_on: + n8n-postgres-init: + condition: service_completed_successfully healthcheck: test: ["CMD-SHELL", "pg_isready -h localhost -U ${N8N_POSTGRES_USER} -d ${N8N_POSTGRES_DB}"] interval: 5s @@ -27,7 +37,7 @@ services: retries: 10 initContainer: - image: busybox + image: busybox:1.38.0 container_name: n8n-init command: ["sh", "-c", "chown -R 1000:1000 /home/node/.n8n"] volumes: @@ -76,6 +86,3 @@ networks: external: false proxy: external: true - -volumes: - n8n_postgres_storage: diff --git a/docker/dashboard/homepage/config/services.yaml b/docker/dashboard/homepage/config/services.yaml index a2bad358..171c44eb 100644 --- a/docker/dashboard/homepage/config/services.yaml +++ b/docker/dashboard/homepage/config/services.yaml @@ -25,9 +25,9 @@ description: Remote Desktop connection icon: windows-10.png -- Arr: - - qBittorrent @ NAS: - href: https://qbittorrent.{{HOMEPAGE_VAR_MYDOMAIN}}/ - siteMonitor: https://qbittorrent.{{HOMEPAGE_VAR_MYDOMAIN}}/ - description: "BitTorrent client" - icon: qbittorrent.png +# - Arr: +# - qBittorrent @ NAS: +# href: https://qbittorrent.{{HOMEPAGE_VAR_MYDOMAIN}}/ +# siteMonitor: https://qbittorrent.{{HOMEPAGE_VAR_MYDOMAIN}}/ +# description: "BitTorrent client" +# icon: qbittorrent.png diff --git a/docker/storage/filebrowser/config/filebrowser.db b/docker/database/mongodb/config/initdb/.gitkeep similarity index 100% rename from docker/storage/filebrowser/config/filebrowser.db rename to docker/database/mongodb/config/initdb/.gitkeep diff --git a/docker/database/mongodb/mongodb.yaml b/docker/database/mongodb/mongodb.yaml new file mode 100644 index 00000000..730b53ef --- /dev/null +++ b/docker/database/mongodb/mongodb.yaml @@ -0,0 +1,59 @@ +# MongoDB is a source-available, document-oriented NoSQL database designed for scalability and +# developer agility, storing data as flexible, JSON-like documents rather than rows and columns. +# +# This is a shared instance, following the same pattern as postgresql/couchdb in this category. +# Mongo's /docker-entrypoint-initdb.d scripts only run once, on first startup with an empty data +# directory — each consuming service should add its own init script under ./config/initdb when it +# first starts using this instance. A service added *after* this instance already has data must +# create its user/db manually via `mongosh` instead, since init scripts won't re-run. +# +# Links: +# - Home: https://www.mongodb.com +# - Source: https://github.com/docker-library/mongo +# - Docs: https://hub.docker.com/_/mongo +# +# TODO: Add a mongo-express (or similar) admin UI service if a web GUI is wanted +# TODO: Implement an automated backup solution (mongodump) +--- +name: mongodb +services: + mongodb-init: + image: busybox:1.38.0 + container_name: mongodb-init + command: ["sh", "-c", "chown -R 999:999 /data/db"] + volumes: + - ${DOCKER_VOLUMES}/mongodb/data:/data/db + + mongodb: + image: mongo:7.0.37-jammy + container_name: mongodb + restart: unless-stopped + # kics-scan ignore-block + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USERNAME:-root} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD:?MongoDB root password must be set} + volumes: + - ${DOCKER_VOLUMES}/mongodb/data:/data/db + - ./config/initdb:/docker-entrypoint-initdb.d + ports: + - 27017:27017 + networks: + - proxy + depends_on: + mongodb-init: + condition: service_completed_successfully + healthcheck: + test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping')"] + interval: 10s + timeout: 5s + retries: 5 + labels: + traefik.enable: false + homepage.group: Storage + homepage.name: MongoDB + homepage.icon: mongodb.png + homepage.description: "Popular NoSQL document database" + +networks: + proxy: + external: true diff --git a/docker/database/mssql-server/mssql-server.yaml b/docker/database/mssql-server/mssql-server.yaml index 2cac1290..45b00660 100644 --- a/docker/database/mssql-server/mssql-server.yaml +++ b/docker/database/mssql-server/mssql-server.yaml @@ -12,6 +12,13 @@ --- name: mssql-server services: + mssql-server-init: + image: busybox:1.38.0 + container_name: mssql-server-init + command: ["sh", "-c", "chown -R 10001:10001 /var/opt/mssql"] + volumes: + - ${DOCKER_VOLUMES}/mssql-server/data:/var/opt/mssql + mssql-server: image: mcr.microsoft.com/mssql/server:2025-latest container_name: mssql-server @@ -27,7 +34,10 @@ services: ports: - "1433:1433" volumes: - - mssql_data:/var/opt/mssql + - ${DOCKER_VOLUMES}/mssql-server/data:/var/opt/mssql + depends_on: + mssql-server-init: + condition: service_completed_successfully labels: traefik.enable: false homepage.group: Storage @@ -35,6 +45,3 @@ services: homepage.icon: microsoft-sql-server.png homepage.href: mssql://${LOCAL_NODE_IP:-127.0.0.1}:1433 homepage.description: Enterprise relational database - -volumes: - mssql_data: diff --git a/docker/database/postgresql/postgresql.yaml b/docker/database/postgresql/postgresql.yaml index d855c5d6..65c328fc 100644 --- a/docker/database/postgresql/postgresql.yaml +++ b/docker/database/postgresql/postgresql.yaml @@ -15,6 +15,13 @@ --- name: postgresql services: + postgresql-init: + image: busybox:1.38.0 + container_name: postgresql-init + command: ["sh", "-c", "chown -R 999:999 /var/lib/postgresql"] + volumes: + - ${DOCKER_VOLUMES}/postgresql/data:/var/lib/postgresql + postgresql: image: postgres:18.4-trixie container_name: postgresql @@ -27,12 +34,15 @@ services: POSTGRES_DB: ${POSTGRES_DB:-postgres} POSTGRES_INITDB_ARGS: --data-checksums volumes: - - postgres-data:/var/lib/postgresql + - ${DOCKER_VOLUMES}/postgresql/data:/var/lib/postgresql - ./config/initdb:/docker-entrypoint-initdb.d ports: - 5432:5432 networks: - proxy + depends_on: + postgresql-init: + condition: service_completed_successfully healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"] interval: 10s @@ -45,9 +55,6 @@ services: homepage.icon: postgresql.png homepage.description: "Powerful open source object-relational database system" -volumes: - postgres-data: - networks: proxy: external: true diff --git a/docker/dev/act-runner/act-runner.yaml b/docker/dev/act-runner/act-runner.yaml new file mode 100644 index 00000000..b8e1a557 --- /dev/null +++ b/docker/dev/act-runner/act-runner.yaml @@ -0,0 +1,51 @@ +# Gitea Runner (formerly act_runner) is the official runner for Gitea Actions. +# It connects to a Gitea instance and executes CI/CD workflows in isolated Docker containers, +# supporting three modes: host-level execution, Docker (bind-mounting the host Docker socket), +# and Docker-in-Docker (DinD) for full isolation. +# +# Links: +# - Home: https://gitea.com/gitea/runner +# - Source: https://gitea.com/gitea/runner +# - Docs: https://docs.gitea.com/usage/actions/act-runner +# - Install instructions URL: https://docs.gitea.com/usage/actions/act-runner +# - Compose: https://docs.gitea.com/usage/actions/act-runner +# +# Registration: +# 1. Navigate to Gitea Admin → Actions → Runners → Create Runner +# 2. Copy the registration token and set GITEA_RUNNER_REGISTRATION_TOKEN in the host .env file +# 3. The runner auto-registers on first start using this token; the resulting .runner file +# persists in the /data volume and is used for all subsequent connections +# 4. To re-register (e.g. after token rotation), stop the container, delete the +# ${DOCKER_VOLUMES}/act-runner/.runner file, and restart +# +# TODO: Pin to a specific version tag instead of 2.0.0 for production stability +# TODO: Use the dind-rootless flavour for production deployments requiring job isolation +# TODO: Configure a shared external cache server across multiple runners to avoid rebuilding on each runner +# TODO: Mount runner images from a local registry or mirror to avoid rate limits on Docker Hub +# TODO: Configure container.valid_volumes to restrict which host paths job containers can mount +# TODO: Join Traefik to the dev-act-runner network in docker/security/traefik/traefik.yaml if a web UI is added +--- +name: act-runner +services: + act-runner: + image: gitea/runner:2.0.0 + container_name: act-runner + restart: unless-stopped + environment: + TZ: ${TIMEZONE} + CONFIG_FILE: /config.yaml + GITEA_INSTANCE_URL: https://gitea.${MYDOMAIN}/ + GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN} + GITEA_RUNNER_NAME: ${GITEA_RUNNER_NAME:-act-runner} + GITEA_RUNNER_LABELS: ${GITEA_RUNNER_LABELS:-docker:docker://node:20-bookworm,ubuntu-latest:docker://node:20-bookworm} + volumes: + - ${DOCKER_VOLUMES}/act-runner:/data + - ./config/config.yaml:/config.yaml:ro + # kics-scan ignore-line + - /var/run/docker.sock:/var/run/docker.sock + networks: + - dev-act-runner + +networks: + dev-act-runner: + external: true diff --git a/docker/dev/act-runner/config/config.yaml b/docker/dev/act-runner/config/config.yaml new file mode 100644 index 00000000..d34c514e --- /dev/null +++ b/docker/dev/act-runner/config/config.yaml @@ -0,0 +1,21 @@ +# Gitea Runner configuration +# See https://gitea.com/gitea/runner/src/branch/main/config.example.yaml + +log: + level: info + +runner: + labels: + - docker:docker://node:20-bookworm + - ubuntu-latest:docker://node:20-bookworm + +container: + # Empty = the runner creates a per-job docker network, which is the only + # way workflow `services:` hostnames (e.g. postgres, qdrant) resolve from + # the job container — the default bridge network has no name resolution. + network: "" + privileged: false + +cache: + host: "" + port: 8088 diff --git a/docker/dev/jupyter-notebook/jupyter-notebook.yaml b/docker/dev/jupyter-notebook/jupyter-notebook.yaml index 975fba2f..cbbaca87 100644 --- a/docker/dev/jupyter-notebook/jupyter-notebook.yaml +++ b/docker/dev/jupyter-notebook/jupyter-notebook.yaml @@ -12,7 +12,7 @@ name: jupyter-notebook services: jupyter-notebook: - image: quay.io/jupyter/minimal-notebook:notebook-7.2.2 + image: quay.io/jupyter/minimal-notebook:notebook-7.6.0 container_name: jupyter-notebook restart: unless-stopped networks: diff --git a/docker/dev/supabase/supabase.yaml b/docker/dev/supabase/supabase.yaml index 80f16d8e..6d1eb075 100644 --- a/docker/dev/supabase/supabase.yaml +++ b/docker/dev/supabase/supabase.yaml @@ -430,6 +430,14 @@ services: - supabase # Comment out everything below this point if you are using an external Postgres database + db-init: + container_name: supabase-db-init + image: busybox:1.38.0 + command: ["sh", "-c", "chown -R 100:101 /var/lib/postgresql/data /etc/postgresql-custom"] + volumes: + - ${DOCKER_VOLUMES}/supabase/db-data:/var/lib/postgresql/data + - ${DOCKER_VOLUMES}/supabase/db-config:/etc/postgresql-custom + db: container_name: supabase-db image: supabase/postgres:15.14.1.142 @@ -443,15 +451,15 @@ services: # Initialize the database settings with JWT_SECRET and JWT_EXP - ./config/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z # PGDATA directory is persisted between restarts - - supabase-db-data:/var/lib/postgresql/data + - ${DOCKER_VOLUMES}/supabase/db-data:/var/lib/postgresql/data # Changes required for internal supabase data such as _analytics - ./config/db/_supabase.sql:/docker-entrypoint-initdb.d/migrations/97-_supabase.sql:Z # Changes required for Analytics support - ./config/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z # Changes required for Pooler support - ./config/db/pooler.sql:/docker-entrypoint-initdb.d/migrations/99-pooler.sql:Z - # Use named volume to persist pgsodium decryption key between restarts - - supabase-db-config:/etc/postgresql-custom + # Persists pgsodium decryption key between restarts + - ${DOCKER_VOLUMES}/supabase/db-config:/etc/postgresql-custom healthcheck: test: [ @@ -466,6 +474,8 @@ services: timeout: 5s retries: 10 depends_on: + db-init: + condition: service_completed_successfully vector: condition: service_healthy environment: @@ -578,10 +588,6 @@ services: networks: - supabase -volumes: - supabase-db-config: - supabase-db-data: - networks: supabase: external: false diff --git a/docker/fileshare/qbittorrent/qbittorrent.yaml b/docker/fileshare/qbittorrent/qbittorrent.yaml index 8fdefb98..9add9b4a 100644 --- a/docker/fileshare/qbittorrent/qbittorrent.yaml +++ b/docker/fileshare/qbittorrent/qbittorrent.yaml @@ -3,6 +3,12 @@ # # Recommended setup steps: # - Configure "Bypass authentication for clients in whitelisted IP subnets" (Settings -> Webui) - Authelia handles the authentication +# - Reverse proxy setup (default below, via Traefik): enable "Use HTTP for WebUI" -> "Reverse proxy support", and +# add the Docker proxy network CIDR (e.g. 172.16.0.0/12) to "Trusted proxies list", so qBittorrent reads the +# real client IP from X-Forwarded-For instead of seeing Traefik's container IP. Without this, the subnet +# whitelist never matches and every request is prompted for login. +# - Direct host exposure (see "separate host" note below): leave "Reverse proxy support" disabled, since +# qBittorrent sees the client's real IP directly and the subnet whitelist works as-is. # - The image is build locally (see: `Dockerfile`) and includes the VueTorrent (https://github.com/WDaan/VueTorrent) web interface. To enable it, point your alternate WebUI location to '/usr/vuetorrent' folder in qBittorrent settings. # - Configure categories (Settings menu) to specify default download locations # - Setup forwarding of port 6881 (TCP, UDP) in the router, check open port: https://www.yougetsignal.com/tools/open-ports/ diff --git a/docker/infra/unifi-controller/unifi-controller.yaml b/docker/infra/unifi-controller/unifi-controller.yaml deleted file mode 100644 index 81d11262..00000000 --- a/docker/infra/unifi-controller/unifi-controller.yaml +++ /dev/null @@ -1,62 +0,0 @@ -# The Unifi-controller software is a powerful, enterprise wireless software engine ideal for high-density client deployments requiring low latency and high uptime performance. -# -# For Unifi to adopt other devices, e.g. an Access Point, it is required to change the *inform* IP address. -# Because Unifi runs inside Docker by default it uses an IP address not accessible by other devices. -# To change this go to Settings > System Settings > Controller Configuration and set the Controller Hostname/IP to a hostname or IP address accessible by your devices. -# -# For the Homepage widget use a local account that has read privileges. -# Local account can be created on the Legacy Interface: -# - To temporally switch to the old interface: Settings -> System -> Legacy Interface -> Enable -# - Create the user on Settings -> Admins -# - Set the `${UNIFI_LOCAL_VIEWONLY_USERNAME}` and `${UNIFI_LOCAL_VIEWONLY_PASSWORD}` variables -# -# Links: -# - Home: https://ui.com/download/releases/network-server -# - Image: https://hub.docker.com/r/linuxserver/unifi-controller ---- -name: unifi-controller -services: - unifi-controller: - image: lscr.io/linuxserver/unifi-controller:8.0.24 - container_name: unifi-controller - restart: unless-stopped - environment: - PUID: ${PUID} - PGID: ${PGID} - TZ: ${TIMEZONE} - MEM_LIMIT: 1024 # optional - MEM_STARTUP: 1024 # optional - volumes: - - ${DOCKER_VOLUMES}/unifi-controller:/config - ports: - - 8443:8443 # Unifi web admin port - - 3478:3478/udp # Unifi STUN port - - 10001:10001/udp # Required for AP discovery - - 8080:8080 # Required for device communication - # - 1900:1900/udp # optional - Required for Make controller discoverable on L2 network option - Error response from daemon: driver failed programming external connectivity on endpoint unifi-controllroller: Bind for 0.0.0.0:1900 failed: port is already allocated - - 8843:8843 # optional - Unifi guest portal HTTPS redirect port - - 8880:8880 # optional - Unifi guest portal HTTP redirect port - - 6789:6789 # optional - For mobile throughput test - - 5514:5514/udp # optional - Remote syslog port - networks: - - proxy - labels: - traefik.enable: true - traefik.http.routers.unifi-controller.middlewares: localaccess@file - traefik.http.services.unifi-controller.loadbalancer.server.port: 8443 - traefik.http.services.unifi-controller.loadbalancer.server.scheme: https - traefik.http.services.unifi-controller.loadbalancer.serversTransport: insecureTransport@file - homepage.group: Infra - homepage.name: Unifi Controller - homepage.icon: unifi-controller.png - homepage.href: https://unifi-controller.${MYDOMAIN}/ - homepage.description: Wireless software engine - homepage.widget.type: unifi - homepage.widget.url: https://unifi-controller.${MYDOMAIN} - homepage.widget.username: ${UNIFI_LOCAL_VIEWONLY_USERNAME} - homepage.widget.password: ${UNIFI_LOCAL_VIEWONLY_PASSWORD} - # - homepage.widget.site: Site Name # optional - -networks: - proxy: - external: true diff --git a/docker/infra/unifi-network-application/unifi-network-application.yaml b/docker/infra/unifi-network-application/unifi-network-application.yaml new file mode 100644 index 00000000..aa54941d --- /dev/null +++ b/docker/infra/unifi-network-application/unifi-network-application.yaml @@ -0,0 +1,93 @@ +# UniFi Network Application is a powerful, enterprise wireless software engine ideal for high-density client deployments requiring low latency and high uptime performance. +# +# For Unifi to adopt other devices, e.g. an Access Point, it is required to change the *inform* IP address. +# Because Unifi runs inside Docker by default it uses an IP address not accessible by other devices. +# To change this go to Settings > System Settings > Controller Configuration and set the Controller Hostname/IP to a hostname or IP address accessible by your devices. +# +# For the Homepage widget use a local account that has read privileges. +# Local account can be created on the Legacy Interface: +# - To temporally switch to the old interface: Settings -> System -> Legacy Interface -> Enable +# - Create the user on Settings -> Admins +# - Set the `${UNIFI_LOCAL_VIEWONLY_USERNAME}` and `${UNIFI_LOCAL_VIEWONLY_PASSWORD}` variables +# +# This service no longer bundles its own database — it connects to the shared `mongodb` service +# in `docker/database/mongodb`. That stack must be running before this one starts. +# +# MIGRATION: Migrated from the deprecated linuxserver/unifi-controller image (8.0.24). In-place upgrade +# is not supported — this must be a fresh install restored from a backup of the old instance. +# Before first start, create the Mongo user on the shared instance (per LinuxServer's documented +# init script): +# docker exec -it mongodb mongosh -u root -p "$MONGO_ROOT_PASSWORD" --eval ' +# db.getSiblingDB("admin").createUser({ +# user: "", pwd: "", +# roles: [ +# "clusterMonitor", +# {role:"dbOwner", db:""}, +# {role:"dbOwner", db:"_stat"}, +# {role:"dbOwner", db:"_audit"}, +# {role:"dbOwner", db:"_restore"} +# ] +# })' +# Must be created against `admin` (not `unifi`) to match MONGO_AUTHSOURCE=admin below, and needs +# more than just the main db (`_stat`, `_audit`, `_restore`, `clusterMonitor` too) +# The databases themselves don't need to pre-exist — Mongo creates them lazily on first write. +# +# Links: +# - Home: https://ui.com/download/releases/network-server +# - Image: https://github.com/linuxserver/docker-unifi-network-application +--- +name: unifi-network-application +services: + unifi-network-application: + image: lscr.io/linuxserver/unifi-network-application:10.4.57 + container_name: unifi-network-application + restart: unless-stopped + environment: + PUID: ${PUID} + PGID: ${PGID} + TZ: ${TIMEZONE} + MEM_LIMIT: 1024 # optional + MEM_STARTUP: 1024 # optional + MONGO_HOST: mongodb + MONGO_PORT: 27017 + MONGO_USER: ${UNIFI_MONGO_USERNAME} + MONGO_PASS: ${UNIFI_MONGO_PASSWORD} + MONGO_DBNAME: ${UNIFI_MONGO_DBNAME} + MONGO_AUTHSOURCE: admin + volumes: + - ${DOCKER_VOLUMES}/unifi-network-application:/config + ports: + - 8443:8443 # Unifi web admin port + - 3478:3478/udp # Unifi STUN port + - 10001:10001/udp # Required for AP discovery + - 8080:8080 # Required for device communication + # - 1900:1900/udp # optional - Required for Make controller discoverable on L2 network option - Error response from daemon: driver failed programming external connectivity on endpoint unifi-controllroller: Bind for 0.0.0.0:1900 failed: port is already allocated + - 8843:8843 # optional - Unifi guest portal HTTPS redirect port + - 8880:8880 # optional - Unifi guest portal HTTP redirect port + - 6789:6789 # optional - For mobile throughput test + - 5514:5514/udp # optional - Remote syslog port + networks: + - proxy + labels: + traefik.enable: true + # Explicit rule needed: Traefik's defaultRule derives the host from the compose service name + # (unifi-network-application), but the external hostname is unifi.${MYDOMAIN}. + traefik.http.routers.unifi-network-application.rule: Host(`unifi.${MYDOMAIN}`) + traefik.http.routers.unifi-network-application.middlewares: localaccess@file + traefik.http.services.unifi-network-application.loadbalancer.server.port: 8443 + traefik.http.services.unifi-network-application.loadbalancer.server.scheme: https + traefik.http.services.unifi-network-application.loadbalancer.serversTransport: insecureTransport@file + homepage.group: Infra + homepage.name: UniFi Network Application + homepage.icon: unifi-controller.png + homepage.href: https://unifi.${MYDOMAIN}/ + homepage.description: Wireless software engine + homepage.widget.type: unifi + homepage.widget.url: https://unifi.${MYDOMAIN} + homepage.widget.username: ${UNIFI_LOCAL_VIEWONLY_USERNAME} + homepage.widget.password: ${UNIFI_LOCAL_VIEWONLY_PASSWORD} + # - homepage.widget.site: Site Name # optional + +networks: + proxy: + external: true diff --git a/docker/security/authelia/authelia.yaml b/docker/security/authelia/authelia.yaml index 25ea4d88..2b3c2c4e 100644 --- a/docker/security/authelia/authelia.yaml +++ b/docker/security/authelia/authelia.yaml @@ -23,7 +23,7 @@ services: authelia: container_name: authelia - image: authelia/authelia:4.39 + image: authelia/authelia:4.39.20 restart: unless-stopped environment: TZ: ${TIMEZONE} diff --git a/docker/storage/bees/bees.yaml b/docker/storage/bees/bees.yaml index 6e62464a..81c4564a 100644 --- a/docker/storage/bees/bees.yaml +++ b/docker/storage/bees/bees.yaml @@ -7,7 +7,7 @@ name: bees services: initContainer: - image: busybox + image: busybox:1.38.0 container_name: beesd-init command: /bin/sh -c "echo 'UUID=${BEES_BTRFS_UUID}' > /etc/bees/disk.conf && echo 'DB_SIZE=${BEES_DB_SIZE}' >> /etc/bees/disk.conf" volumes: diff --git a/docker/storage/filebrowser/config/settings.json b/docker/storage/filebrowser/config/settings.json deleted file mode 100644 index 29ff1d03..00000000 --- a/docker/storage/filebrowser/config/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "port": 80, - "baseURL": "", - "address": "", - "log": "stdout", - "database": "/database/filebrowser.db", - "root": "/srv" - } diff --git a/docker/storage/filebrowser/filebrowser.yaml b/docker/storage/filebrowser/filebrowser.yaml deleted file mode 100644 index 2ae3199c..00000000 --- a/docker/storage/filebrowser/filebrowser.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. -# -# Default credentials: `admin` / `admin` -# -# Links: -# - Home: https://filebrowser.org -# - Source: https://github.com/filebrowser/filebrowser ---- -name: filebrowser -services: - filebrowser: - image: filebrowser/filebrowser:s6 - container_name: filebrowser - restart: unless-stopped - environment: - PUID: ${PUID} - PGID: ${PGID} - TZ: ${TIMEZONE} - volumes: - - ${STORAGE_ALL}:/srv - - ${DOCKER_VOLUMES}/filebrowser/filebrowser.db:/database/filebrowser.db - - ${DOCKER_VOLUMES}/filebrowser/settings.json:/config/settings.json - networks: - - proxy - labels: - traefik.enable: true - traefik.http.routers.filebrowser.middlewares: localaccess@file - traefik.http.services.filebrowser.loadbalancer.server.port: 80 - homepage.group: Storage - homepage.name: Filebrowser - homepage.icon: filebrowser.png - homepage.href: https://filebrowser.${MYDOMAIN}/ - homepage.description: File Management - -networks: - proxy: - external: true diff --git a/docker/tools/searxng/searxng.yaml b/docker/tools/searxng/searxng.yaml index 4b554d8c..ac93fbda 100644 --- a/docker/tools/searxng/searxng.yaml +++ b/docker/tools/searxng/searxng.yaml @@ -18,15 +18,16 @@ services: interval: 30s timeout: 10s retries: 3 - # Named volume is needed to avoid "chown - Operation not permitted" error volumes: - - searxng-valkey-data:/data + - ${DOCKER_VOLUMES}/searxng/valkey-data:/data cap_drop: - ALL cap_add: - SETGID - SETUID - DAC_OVERRIDE + - CHOWN + - FOWNER logging: driver: "json-file" options: @@ -37,7 +38,7 @@ services: searxng: container_name: searxng - image: docker.io/searxng/searxng:2025.5.30-37d851f + image: docker.io/searxng/searxng:2026.7.3-747cec4c2 restart: unless-stopped volumes: - ${DOCKER_VOLUMES}/searxng:/etc/searxng @@ -73,6 +74,3 @@ networks: external: false proxy: external: true - -volumes: - searxng-valkey-data: diff --git a/docs/PRPs/containers/act-runner.md b/docs/PRPs/containers/act-runner.md new file mode 100644 index 00000000..93284164 --- /dev/null +++ b/docs/PRPs/containers/act-runner.md @@ -0,0 +1,155 @@ +## Base information for Act Runner application + +Application name: Act Runner (Gitea Runner) +Homepage: https://gitea.com/gitea/runner +GitHub page: https://github.com/go-gitea/gitea (main Gitea project only; runner repo is on Gitea at https://gitea.com/gitea/runner) +Install instructions URL: https://docs.gitea.com/usage/actions/act-runner +Container image(s): gitea/runner:latest (old deprecated image: gitea/act_runner) +Category: dev +Dashboard Icon: https://gitea.com/assets/img/favicon.svg +Dashboard Group: Development +Short description: Runner for Gitea Actions CI/CD +Long description: Gitea Runner (formerly act_runner) is the official runner for Gitea Actions. It connects to a Gitea instance and executes CI/CD workflows in isolated Docker containers, supporting three modes: host-level execution, Docker (bind-mounting the host Docker socket), and Docker-in-Docker (DinD) for full isolation. + +## Container deployment + +### Docker Compose Configuration + +The Gitea documentation provides a Docker Compose example using the (now deprecated) `gitea/act_runner` image. The project has since moved to `gitea/runner`. Use the updated image: + +```yaml +services: + runner: + image: docker.io/gitea/runner:nightly + environment: + CONFIG_FILE: /config.yaml + GITEA_INSTANCE_URL: "${INSTANCE_URL}" + GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}" + GITEA_RUNNER_NAME: "${RUNNER_NAME}" + GITEA_RUNNER_LABELS: "${RUNNER_LABELS}" + volumes: + - ./config.yaml:/config.yaml + - ./data:/data + - /var/run/docker.sock:/var/run/docker.sock +``` + +#### Ephemeral Runner Mode + +For enhanced security, ephemeral runners register for a single job and tear down after completion: + +```yaml +services: + runner: + image: docker.io/gitea/runner:nightly + environment: + CONFIG_FILE: /config.yaml + GITEA_INSTANCE_URL: "${INSTANCE_URL}" + GITEA_RUNNER_REGISTRATION_TOKEN: "${REGISTRATION_TOKEN}" + GITEA_RUNNER_NAME: "${RUNNER_NAME}" + GITEA_RUNNER_LABELS: "${RUNNER_LABELS}" + GITEA_RUNNER_EPHEMERAL: "1" + volumes: + - ./config.yaml:/config.yaml + - /var/run/docker.sock:/var/run/docker.sock +``` + +No `/data` volume is needed for ephemeral runners since credentials are single-use. + +### Image Flavours + +Three flavours are available, all from the same Dockerfile: + +| Tag | Build Target | Base Image | Docker Daemon | Supervisor | Runs As | +|-----|-------------|------------|--------------|------------|---------| +| `latest` (or ``) | `basic` | Alpine | External (host socket) | tini | root | +| `latest-dind` | `dind` | docker:dind | Bundled (privileged) | s6 | root (privileged) | +| `latest-dind-rootless` | `dind-rootless` | docker:dind-rootless | Bundled (rootless) | s6 | rootless (UID 1000) | + +- **basic** (`latest`): Minimal Alpine image, no bundled Docker daemon. Bind-mount the host's Docker socket. Does not need `--privileged`. +- **dind** (`latest-dind`): Bundles its own Docker daemon. Requires `--privileged`. Fully isolated from host daemon. +- **dind-rootless** (`latest-dind-rootless`): Runs both daemon and runner as unprivileged user. Reduced blast radius, but has rootless Docker limitations (networking, cgroups, storage drivers). + +### Environment Variables + +Required: +- `GITEA_INSTANCE_URL` — URL of the Gitea instance (e.g., `https://gitea.example.com`) +- `GITEA_RUNNER_REGISTRATION_TOKEN` — Token obtained from Gitea admin/runners settings + +Optional: +- `GITEA_RUNNER_NAME` — Runner display name (default: hostname) +- `GITEA_RUNNER_LABELS` — Comma-separated labels (e.g., `ubuntu-latest:docker://...`) +- `GITEA_RUNNER_EPHEMERAL` — Set to `1` for single-job ephemeral mode +- `GITEA_RUNNER_ONCE` — Set to `1` to run one job then exit +- `CONFIG_FILE` — Path to YAML config file inside container +- `GITEA_MAX_REG_ATTEMPTS` — Max registration retry attempts +- `RUNNER_STATE_FILE` — Path to runner state file +- `GITEA_RUNNER_REGISTRATION_TOKEN_FILE` — Path to file containing registration token + +### Configuration File + +Generate a default config: + +```bash +docker run --entrypoint="" --rm -it gitea/runner:latest gitea-runner generate-config > config.yaml +``` + +Key config options (see `config.example.yaml` in the repo for full reference): +- `runner.labels` — Job labels and their container mappings +- `container.network` — Docker network for job containers (bridge, host, or custom) +- `container.privileged` — Run job containers in privileged mode +- `container.options` — Additional Docker container launch options +- `container.valid_volumes` — Allowed volume mount sources +- `cache.host` / `cache.port` — External cache server settings +- `log.log_level` — Runner log level (info, debug, trace) + +### Cache Configuration + +Each runner starts its own cache server automatically for `actions/cache`. Cache entries are local to that runner. For a shared cache across runners, run a dedicated `cache-server`: + +```bash +gitea-runner -c cache-server-config.yaml cache-server +``` + +With the cache server config: +```yaml +cache: + dir: /data/actcache + port: 8088 + external_secret: "replace-with-a-strong-random-secret" +``` + +Point each runner's config at the cache server: +```yaml +cache: + external_server: "http://:8088/" + external_secret: "replace-with-a-strong-random-secret" +``` + +### Security Considerations + +1. **Docker Socket Mount**: Bind-mounting `/var/run/docker.sock` gives the runner (and any job it runs) access to the host Docker daemon. Jobs could potentially escape the container. Use the DinD flavour for better isolation. + +2. **CVE-2026-58053 (Container Escape)**: A vulnerability exists in act_runner (also present in the renamed runner) where `container.options` from workflow YAML is passed to `HostConfig` even when `privileged: false`. Options like `--pid=host`, `--ipc=host`, `--cap-add=ALL`, `--security-opt seccomp=unconfined` can still be set. Mitigations: + - Use Docker-in-Docker rootless mode for better isolation + - Use ephemeral runners for one-shot jobs + - Do not share Docker-backed runners with untrusted repositories + - Treat `container.options` as untrusted input + +3. **Ephemeral Mode**: Enabling `GITEA_RUNNER_EPHEMERAL=1` ensures each runner registers for exactly one job, reducing credential exposure. + +4. **Registration Token**: The registration token allows registering new runners. Protect it and rotate it regularly. Store it in a `.env` file or use `GITEA_RUNNER_REGISTRATION_TOKEN_FILE` to read from a file. + +5. **Network Isolation**: Job containers run on a separate Docker network by default (`bridge`). For runners that need to reach the Gitea instance by hostname, consider a custom Docker network. + +### AMD GPU Acceleration + +None. The Gitea Runner is a CI/CD job orchestrator; it does not perform GPU-accelerated work itself. GPU acceleration for workloads inside job containers (e.g., for model training or video encoding) would be configured at the workflow level within the job container, not at the runner level. + +### Further Improvements + +- Pin the runner to a specific version tag instead of `nightly` for production stability +- Use the `dind-rootless` flavour for production deployments requiring job isolation +- Configure a shared external cache server across multiple runners to avoid rebuilding on each runner +- Mount runner images from a local registry or mirror to avoid rate limits on Docker Hub +- Set up a dedicated Docker network for the runner and Gitea instance to reduce latency +- Configure `container.valid_volumes` to restrict which host paths job containers can mount diff --git a/docs/PRPs/containers/mongodb.md b/docs/PRPs/containers/mongodb.md new file mode 100644 index 00000000..ec23532e --- /dev/null +++ b/docs/PRPs/containers/mongodb.md @@ -0,0 +1,78 @@ +## Base information for MongoDB application + +Application name: MongoDB +Homepage: https://www.mongodb.com +GitHub page: https://github.com/docker-library/mongo +Install instructions URL: https://hub.docker.com/_/mongo +Category: database +Dashboard Icon: mongodb.png +Dashboard Group: Storage +Short description: Popular NoSQL document database +Long description: MongoDB is a source-available, document-oriented NoSQL database designed for scalability and developer agility, storing data as flexible, JSON-like documents rather than rows and columns. + +## Container deployment + +### Compose example (from Docker Hub `_/mongo`) + +```yaml +services: + mongo: + image: mongo + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example + volumes: + - /my/own/datadir:/data/db + ports: + - 27017:27017 +``` + +(`mongo-express` admin UI is offered in the official example too, but is not required — skip it +unless a web admin UI is wanted later.) + +### Environment variables + +- `MONGO_INITDB_ROOT_USERNAME` / `MONGO_INITDB_ROOT_PASSWORD` — creates the root user on first + boot. Must be treated as secrets (host `.env`, not example `.env`). +- `MONGO_INITDB_DATABASE` — optional, names the database that init scripts in + `/docker-entrypoint-initdb.d/` operate against. + +### Volumes + +- `/data/db` — database data storage (persistent). +- `/data/configdb` — only relevant with `--configsvr` (sharding), not needed here. + +### Init scripts (`/docker-entrypoint-initdb.d`) + +Any `.sh`/`.js` files mounted here run **once**, only on first startup when `/data/db` is empty. +This instance is intended to be a **shared** Mongo server (same pattern as +`docker/database/postgresql` and `docker/database/couchdb` in this repo) — the first consuming +application is `unifi-network-application`, which needs its own non-root user + `unifi`/`unifi_stat` +databases created via RBAC. Since this is a brand-new instance, it's fine to mount that app's +init script (creating the `unifi` user with `dbOwner` on `unifi`/`unifi_stat`, per LinuxServer's +`docker-unifi-network-application` README) as part of this initial deployment. **Any future +service** that wants to reuse this same Mongo instance will need its db/user created manually via +`mongosh` after the fact, since init scripts won't re-run against existing data — call this out in +the compose file's header comment as a TODO/note for future maintainers. + +### Security considerations + +- By default Mongo requires no authentication — root credentials **must** be set via + `MONGO_INITDB_ROOT_USERNAME`/`_PASSWORD` env vars (real secrets, host-specific `.env`). + Enable RBAC/auth from the start rather than relaxing it later. +- Do not expose port 27017 to the internet/reverse proxy; if published, keep it local-network-only + (no Traefik router — same as `postgresql.yaml`'s `traefik.enable: false`). Other containers reach + it over the shared `proxy` docker network by container name (`mongodb`), not via the published + port. + +### AMD GPU acceleration + +None — MongoDB has no GPU acceleration support (not applicable to a database engine). + +### Possible further improvements + +- Add a `mongo-express` or similar admin UI service later if a web GUI is wanted (mirrors the + `adminer` service already present for SQL databases in `docker/database/`). +- Add an automated backup solution (`mongodump` cron) similar to the TODOs already present in + `docker/database/postgresql/postgresql.yaml`.