NAS 上的 docker-compose 代码库

这里是我公众号 「辣丝刀客」已发表 61 篇 NAS 教程里出现过的 docker-compose(yml)全文,共 79 份。
按用途分了 10 类;每份独立成块,右上角一键复制、粘进 docker-compose.yml 就能跑;卡片抬头点一下就回原文。
👇 整包拿走:79 份 compose + 配套 .env(zip) · 纯文本版

相册与照片 5 段

volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
需要的变量(.env)
UPLOAD_LOCATION=   ← 这里要你填
services:
  immich-server:
    image: ghcr.io/immich-app/immich-server:release
    command: ['start.sh', 'immich']
    volumes:
      - ./library:/usr/src/app/upload
      - ./config:/config  # 保留给自定义配置(可选)
    env_file:
      - .env
    ports:
      - 2283:3001
    depends_on:
      - redis
      - database
‘    restart: unless-stopped

  immich-microservices:
    image: ghcr.io/immich-app/immich-server:release
    command: ['start.sh', 'immich-microservices']
    volumes:
      - ./library:/usr/src/app/upload
      - ./config:/config
    env_file:
      - .env
    depends_on:
      - redis
      - database
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  database:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
      POSTGRES_DB: immich
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    restart: unless-stopped
services:
  app:
    image: ghcr.io/hotio/demo-app
    container_name: demo-app
    volumes:
      # 配置和上传都要挂出来
      - /vol1/1000/docker/app:/config
      - /vol1/1000/photos:/data/upload

  app-db:
    image: postgres:16-alpine
    container_name: app-db
    volumes:
      # 数据库目录,主机侧写短点
      - /vol1/pg:/var/lib/postgresql/data

  memos:
    image: neosmemo/memos:stable
    container_name: memos
    volumes:
      # 具名卷:跨容器共享用
      - memos-data:/var/opt/memos

volumes:
  memos-data:
immich-server:
    image: ${SRV}
    container_name: immich_server
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - DB_HOSTNAME=immich-db
      - DB_USERNAME=postgres
      - DB_DATABASE_NAME=immich
      - DB_PASSWORD=${DBPASS}
      - REDIS_HOSTNAME=immich-redis
    volumes:
      - ${IML}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "2283:2283"
    depends_on:
      - immich-redis
      - immich-db
    restart: unless-stopped

  immich-machine-learning:
    image: ${ML}
    container_name: immich_ml
    environment:
      - TZ=${TZ}
    volumes:
      - ml-cache:/cache
    restart: unless-stopped

  immich-redis:
    image: ${REDIS}
    container_name: immich_redis
    restart: unless-stopped

  immich-db:
    image: ${DB}
    container_name: immich_db
    environment:
      - POSTGRES_PASSWORD=${DBPASS}
      - POSTGRES_USER=postgres
      - POSTGRES_DB=immich
    volumes:
      - ${IMD}:/var/lib/postgresql/data
    restart: unless-stopped
需要的变量(.env)
PUID=1000
PGID=1000
TZ=Asia/Shanghai
DB=${IMG}/postgres:${TAG}
DBPASS=改成你的强数据库密码
GH=ghcr.io
IMD=${ROOT}/immich/pgdata
IMG=${GH}/immich-app
IML=${ROOT}/immich/library
ML=${IMG}/immich-machine-learning:release
REDIS=docker.io/valkey/valkey:8-bookworm
ROOT=/vol1/1000/docker
SRV=${IMG}/immich-server:release
TAG=14-vectorchord0.4.3-pgvectors0.2.0
services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${JFD}:/config
      - ${DATA}/media:/media:ro
    ports:
      - "8096:8096"
    devices:
      - /dev/dri:/dev/dri
    restart: unless-stopped

  immich-server:
    image: ${SRV}
    container_name: immich_server
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - DB_HOSTNAME=immich-db
      - DB_USERNAME=postgres
      - DB_DATABASE_NAME=immich
      - DB_PASSWORD=${DBPASS}
      - REDIS_HOSTNAME=immich-redis
    volumes:
      - ${IML}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "2283:2283"
    depends_on:
      - immich-redis
      - immich-db
    restart: unless-stopped

  immich-machine-learning:
    image: ${ML}
    container_name: immich_ml
    environment:
      - TZ=${TZ}
    volumes:
      - ml-cache:/cache
    restart: unless-stopped

  immich-redis:
    image: ${REDIS}
    container_name: immich_redis
    restart: unless-stopped

  immich-db:
    image: ${DB}
    container_name: immich_db
    environment:
      - POSTGRES_PASSWORD=${DBPASS}
      - POSTGRES_USER=postgres
      - POSTGRES_DB=immich
    volumes:
      - ${IMD}:/var/lib/postgresql/data
    restart: unless-stopped

  navidrome:
    image: deluan/navidrome
    container_name: navidrome
    environment:
      - ND_MUSICFOLDER=/music
      - ND_SCANSCHEDULE=1h
      - TZ=${TZ}
    volumes:
      - ${NVD}:/data
      - ${DATA}/music:/music:ro
    ports:
      - "4533:4533"
    restart: unless-stopped

  paperless:
    image: ${PAP}
    container_name: paperless
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - PAPERLESS_REDIS=${PLR}
      - PAPERLESS_URL=https://p.abc.cn
    volumes:
      - ${PPD}:/usr/src/paperless/data
      - ${PPM}:/usr/src/paperless/media
      - ${PPC}:/usr/src/paperless/consume
    ports:
      - "8000:8000"
    depends_on:
      - plredis
    restart: unless-stopped

  plredis:
    image: redis:7
    container_name: plredis
    restart: unless-stopped

  vaultwarden:
    image: vaultwarden/server
    container_name: vaultwarden
    environment:
      - SIGNUPS_ALLOWED=true
      - DOMAIN=https://v.abc.cn
      - TZ=${TZ}
    volumes:
      - ${VWD}:/data
    ports:
      - "8081:80"
    restart: unless-stopped

  filebrowser:
    image: filebrowser/filebrowser
    container_name: filebrowser
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${DATA}/srv:/srv
      - ${FBD}:/database
      - ${FBC}:/config
    ports:
      - "8082:80"
    restart: unless-stopped

volumes:
  ml-cache:
需要的变量(.env)
PUID=1000
PGID=1000
TZ=Asia/Shanghai
DATA=/vol1/1000/data
DB=${IMG}/postgres:${TAG}
DBPASS=改成你的强数据库密码
FBC=${ROOT}/filebrowser/cfg
FBD=${ROOT}/filebrowser/db
GH=ghcr.io
IMD=${ROOT}/immich/pgdata
IMG=${GH}/immich-app
IML=${ROOT}/immich/library
JFD=${ROOT}/jellyfin
ML=${IMG}/immich-machine-learning:release
NVD=${ROOT}/navidrome
PAP=${GH}/${PNX}/${PNX}:latest
PLR=redis://plredis:6379
PNX=paperless-ngx
PPC=${DATA}/scans
PPD=${ROOT}/paperless/data
PPM=${ROOT}/paperless/media
REDIS=docker.io/valkey/valkey:8-bookworm
ROOT=/vol1/1000/docker
SRV=${IMG}/immich-server:release
TAG=14-vectorchord0.4.3-pgvectors0.2.0
VWD=${ROOT}/vaultwarden

影音与下载 20 段

version: "3.8"

services:

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    restart: always
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./prowlarr/config:/config
    ports:
      - "9696:9696"
    network_mode: bridge

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    restart: always
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./sonarr/config:/config
      - /volume1/downloads:/downloads
      - /volume1/video/tv:/tv
    ports:
      - "8989:8989"
    network_mode: bridge

  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    restart: always
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./radarr/config:/config
      - /volume1/downloads:/downloads
      - /volume1/video/movie:/movies
    ports:
      - "7878:7878"
    network_mode: bridge
version: "3.8"

services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - JELLYFIN_PublishedServerUrl=http://192.168.31.2:8096
    volumes:
      - ./config:/config
      - ./cache:/cache
      - /volume1/video/movies:/media/movies
      - /volume1/video/tv:/media/tv
    ports:
      - "8096:8096"
      - "8920:8920"
      - "7359:7359/udp"
      - "1900:1900/udp"
    network_mode: bridge
出处:NAS 影音全自动全家桶部署教程compose65行
version: "3.8"
services:
  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    restart: always
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./prowlarr/config:/config
    ports:
      - "9696:9696"
    network_mode: bridge
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    restart: always
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./sonarr/config:/config
      - /volume1/downloads:/downloads
      - /volume1/video/tv:/tv
    ports:
      - "8989:8989"
    network_mode: bridge
  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    restart: always
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./radarr/config:/config
      - /volume1/downloads:/downloads
      - /volume1/video/movie:/movies
    ports:
      - "7878:7878"
    network_mode: bridge
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - JELLYFIN_PublishedServerUrl=http://192.168.31.2:8096
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /volume1/video/movie:/media/movies
      - /volume1/video/tv:/media/tv
    ports:
      - "8096:8096"
      - "8920:8920"
      - "7359:7359/udp"
      - "1900:1900/udp"
    network_mode: bridge
version: "3.8"
services:
  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./prowlarr/config:/config
    ports:
      - "9696:9696"
    network_mode: bridge
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./sonarr/config:/config
      - /volume1/downloads:/downloads
      - /volume1/video/tv:/tv
    ports:
      - "8989:8989"
    network_mode: bridge
  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./radarr/config:/config
      - /volume1/downloads:/downloads
      - /volume1/video/movie:/movies
    ports:
      - "7878:7878"
    network_mode: bridge
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent/config:/config
      - /volume1/downloads:/downloads
    ports:
      - "8080:8080"
      - "6881:6881"
      - "6881:6881/udp"
    network_mode: bridge
  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./bazarr/config:/config
      - /volume1/video/tv:/tv
      - /volume1/video/movie:/movies
    ports:
      - "6767:6767"
    network_mode: bridge
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
      - JELLYFIN_PublishedServerUrl=http://192.168.31.2:8096
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /volume1/video/movie:/media/movies
      - /volume1/video/tv:/media/tv
    ports:
      - "8096:8096"
      - "8920:8920"
      - "7359:7359/udp"
      - "1900:1900/udp"
    network_mode: bridge
    # 有Intel核显开启硬解,无核显直接注释两行
    # devices:
    #   - /dev/dri/renderD128:/dev/dri/renderD128
version: "3.8"

networks:
  media_net:
    driver: bridge

services:
  redis:
    image: redis:7-alpine
    container_name: mp-redis
    restart: always
    mem_limit: 512m
    volumes:
      - ./redis:/data
    networks:
      - media_net

  postgres:
    image: postgres:18-alpine
    container_name: mp-pg
    restart: always
    volumes:
      - ./postgres:/var/lib/postgresql/data
    ports:
      - "5433:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: moviepilot
      TZ: Asia/Shanghai
    healthcheck:
      test: ["CMD-SHELL","pg_isready -U postgres -d moviepilot"]
      interval: 10s
      timeout: 5s
      retries: 5
    mem_limit: 1g
    networks:
      - media_net

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    restart: always
    environment:
      TZ: Asia/Shanghai
      LOG_LEVEL: info
    ports:
      - "8191:8191"
    networks:
      - media_net

  moviepilot:
    image: jxxghp/moviepilot:latest
    container_name: moviepilot
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
      flaresolverr:
        condition: service_started
    ports:
      - "3000:3000"
    volumes:
      - ./mp_config:/config
      # 【必改】下载目录(与QB统一)
      - /mnt/download:/downloads
      # 【必改】最终媒体库目录(Emby/Jellyfin读取)
      - /mnt/media:/media
    environment:
      TZ: Asia/Shanghai
      PUID: 1000
      PGID: 1000
      DB_TYPE: postgres
      POSTGRES_HOST: postgres
      POSTGRES_PORT: 5432
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: moviepilot
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_DB: 0
      FLARESOLVERR_URL: http://flaresolverr:8191
    networks:
      - media_net

  mdc:
    image: mdcng/mdc:latest
    container_name: mdc
    restart: always
    ports:
      - "9208:9208"
    environment:
      TZ: Asia/Shanghai
      PUID: 1000
      PGID: 1000
      MDC_USERNAME: admin
      MDC_PASSWORD: admin
      FLARESOLVERR_URL: http://flaresolverr:8191
    volumes:
      - ./mdc_config:/config
      - /mnt/download:/downloads
      - /mnt/media:/media
    networks:
      - media_net
version: "3.8"
services:
  moviepilot:
    image: jxxghp/moviepilot:latest
    container_name: moviepilot
    ports:
      - "3000:3000"
    volumes:
      - ./config:/config
      - ./media:/media
      - ./downloads:/downloads
    environment:
      - TZ=Asia/Shanghai
    restart: always
services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /vol2/1000/docker/qbittorrent/config:/config
      - /vol2/1000/downloads:/downloads
    ports:
      - 8080:8080
      - 6881:6881
      - 6881:6881/udp
    restart: unless-stopped

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /vol2/1000/docker/sonarr/config:/config
      - /vol2/1000/downloads:/downloads
      - /vol2/1000/tv:/tv
    ports:
      - 8989:8989
    restart: unless-stopped

  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /vol2/1000/docker/jellyfin/config:/config
      - /vol2/1000/tv:/data/tvshows
    ports:
      - 8096:8096
    restart: unless-stopped
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1000
      - PGID=1001
      - TZ=Asia/Shanghai
    volumes:
      - ./config/jellyfin:/config
      - ./media:/media
    ports:
      - "8097:8096"
    restart: unless-stopped
    networks:
      - media-net

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1001
      - TZ=Asia/Shanghai
      - WEBUI_PORT=8080
    volumes:
      - ./config/qbittorrent:/config
      - ./media:/media
    ports:
      - "8080:8080"
      - "6881:6881"
      - "6881:6881/udp"
    restart: unless-stopped
    networks:
      - media-net

  sonarr:
    image: ghcr.io/hotio/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=1000
      - PGID=1001
      - TZ=Asia/Shanghai
    volumes:
      - ./config/sonarr:/config
      - ./media:/media
    ports:
      - "8989:8989"
    restart: unless-stopped
    networks:
      - media-net

  radarr:
    image: lscr.io/linuxserver/radarr
    container_name: radarr
    environment:
      - PUID=1000
      - PGID=1001
      - TZ=Asia/Shanghai
    volumes:
      - ./config/radarr:/config
      - ./media:/media
    ports:
      - "7878:7878"
    restart: unless-stopped
    networks:
      - media-net

  prowlarr:
    image: ghcr.io/hotio/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=1000
      - PGID=1001
      - TZ=Asia/Shanghai
    volumes:
      - ./config/prowlarr:/config
    ports:
      - "9696:9696"
    restart: unless-stopped
    networks:
      - media-net

  recycler:
    image: python:3.12-alpine
    container_name: media-recycler
    environment:
      - TZ=Asia/Shanghai
      - JF_URL=http://jellyfin:8096
      - JF_TOKEN=改成你的Jellyfin令牌
      - RADARR_URL=http://radarr:7878
      - RADARR_KEY=改成你的Radarr密钥
      - SONARR_URL=http://sonarr:8989
      - SONARR_KEY=改成你的Sonarr密钥
      - KEEP_DAYS=30
      - DRY_RUN=1
    volumes:
      - ./recycler:/app
    working_dir: /app
    command: sh /app/loop.sh
    restart: unless-stopped
    networks:
      - media-net

networks:
  media-net:
    driver: bridge
services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./config/qbittorrent:/config
      - /vol2/1000/downloads:/downloads
    ports:
      - "8080:8080"
      - "6881:6881"
      - "6881:6881/udp"
    restart: unless-stopped
    networks:
      - media-net

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./config/prowlarr:/config
    ports:
      - "9696:9696"
    restart: unless-stopped
    networks:
      - media-net

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./config/sonarr:/config
      - /vol2/1000/tv:/tv
      - /vol2/1000/downloads:/downloads
    ports:
      - "8989:8989"
    restart: unless-stopped
    networks:
      - media-net

  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./config/radarr:/config
      - /vol2/1000/movies:/movies
      - /vol2/1000/downloads:/downloads
    ports:
      - "7878:7878"
    restart: unless-stopped
    networks:
      - media-net

  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./config/jellyfin:/config
      - /vol2/1000/tv:/data/tvshows
      - /vol2/1000/movies:/data/movies
    ports:
      - "8096:8096"
    restart: unless-stopped
    networks:
      - media-net

  bazarr:
    image: lscr.io/linuxserver/bazarr:latest
    container_name: bazarr
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - ./config/bazarr:/config
      - /vol2/1000/tv:/tv
      - /vol2/1000/movies:/movies
      - /vol2/1000/downloads:/downloads
    ports:
      - "6767:6767"
    restart: unless-stopped
    networks:
      - media-net

networks:
  media-net:
    driver: bridge
# 三件套:Jellyfin/Syncthing/Portainer
# 位置 /vol1/1000/docker/nas-stack
# 启动 docker compose up -d

services:

  # ----- 1. 影音:Jellyfin
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    restart: unless-stopped
    # host 网络:DLNA 和局域网发现才可用
    network_mode: host
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - ${MEDIA_DIR}:/media:ro
    # 把核显给容器,硬解才用得起来
    devices:
      - /dev/dri:/dev/dri

  # ----- 2. 同步:Syncthing
  syncthing:
    image: syncthing/syncthing
    container_name: syncthing
    restart: unless-stopped
    # 也用 host 网络,局域网自动发现更顺
    network_mode: host
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ./syncthing/config:/var/syncthing
      - ${SYNC_DIR}:/mnt/sync

  # ----- 3. 面板:Portainer
  portainer:
    image: portainer/portainer-ce
    container_name: portainer
    restart: unless-stopped
    # 用 -H 把面板指向下面挂进来的 socket
    command: -H unix:///sock
    ports:
      - "9000:9000"
    volumes:
      # socket 挂到 /sock,再用 -H
      - /var/run/docker.sock:/sock
      - ./portainer/data:/data
需要的变量(.env)
PUID=1000
PGID=1000
TZ=Asia/Shanghai
MEDIA_DIR=/vol1/1000/media
SYNC_DIR=/vol1/1000/sync
services:
  jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    # 左主机右容器,别写反
    ports:
      # 8080 被占,主机侧换 18080
      - "18080:8096"
      - "7359:7359/udp"
      # 只给本机访问的更安全
      - "127.0.0.1:18920:8920"
# 看主机上谁占着 8080
sudo ss -tlnp | grep 8080
# 看容器现在的端口映射
docker ps --format '{{.Names}} {{.Ports}}'
services:
  sonarr:
    image: ghcr.io/hotio/sonarr
    container_name: sonarr
    volumes:
      # 绝对路径,别用 ./
      - /vol1/1000/docker/sonarr:/config
      - /vol1/1000/media:/media
      - /vol1/1000/downloads:/downloads
mkdir -p /vol1/1000/docker/sonarr
cd /vol1/1000/docker/sonarr
docker compose up -d
# 查当前用户的 uid/gid
id
# 看挂载目录现在归谁
ls -ln /vol1/1000/docker/sonarr
services:
  sonarr:
    image: ghcr.io/hotio/sonarr
    container_name: sonarr
    # 换成 id 查到的数值
    environment:
      - PUID=1000
      - PGID=1000
      - UMASK=002
    volumes:
      - /vol1/1000/docker/sonarr:/config
      - /vol1/1000/media:/media

  app-db:
    image: redis:7-alpine
    container_name: app-db
    # 非 lsio 镜像用 user 指定
    user: "1000:1000"
    volumes:
      - /vol1/1000/docker/app/redis:/data
# 列出所有容器含已停止的
docker ps -a | grep jellyfin
# 删掉残留的同名容器
docker rm -f jellyfin
# 停旧容器并重建,配置才生效
docker compose down
docker compose pull
docker compose up -d
services:
  jellyfin:
    image: jellyfin/jellyfin
    # 项目名加服务名,避免撞名
    container_name: nas-jellyfin
    restart: unless-stopped
services:
  sonarr:
    image: ghcr.io/hotio/sonarr
    container_name: sonarr
    environment:
      # 时区必写,日志才对
      - TZ=Asia/Shanghai
      - LANG=C.UTF-8
      - LC_ALL=C.UTF-8
    volumes:
      - /vol1/1000/docker/sonarr:/config
      # 跟随主机时间,只读
      - /etc/localtime:/etc/localtime:ro
services:
  sonarr:
    # 卡 lscr.io 就换这行
    image: ghcr.io/hotio/sonarr
    container_name: sonarr
services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /vol4/1000/jellyfin:/config
      - /vol4/1000/media:/media
    ports:
      - "8096:8096"
    networks:
      - home

networks:
  home:
    driver: bridge
services:
  navidrome:
    image: deluan/navidrome
    container_name: navidrome
    restart: unless-stopped
    ports:
      - 4533:4533
    environment:
      - ND_MUSICFOLDER=/music
      - ND_SCANSCHEDULE=1h
    volumes:
      - ./data:/data
      - /vol1/1000/music:/music:ro
jellyfin:
    image: jellyfin/jellyfin
    container_name: jellyfin
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${JFD}:/config
      - ${DATA}/media:/media:ro
    ports:
      - "8096:8096"
    devices:
      - /dev/dri:/dev/dri
    restart: unless-stopped
需要的变量(.env)
PUID=1000
PGID=1000
TZ=Asia/Shanghai
DATA=/vol1/1000/data
JFD=${ROOT}/jellyfin
ROOT=/vol1/1000/docker
navidrome:
    image: deluan/navidrome
    container_name: navidrome
    environment:
      - ND_MUSICFOLDER=/music
      - ND_SCANSCHEDULE=1h
      - TZ=${TZ}
    volumes:
      - ${NVD}:/data
      - ${DATA}/music:/music:ro
    ports:
      - "4533:4533"
    restart: unless-stopped
需要的变量(.env)
TZ=Asia/Shanghai
DATA=/vol1/1000/data
NVD=${ROOT}/navidrome
ROOT=/vol1/1000/docker

家庭监控与看护 6 段

version: "3.8"
services:
  mosquitto:
    image: eclipse-mosquitto:2
    restart: always
    ports: ["1883:1883", "9001:9001"]
    volumes: ["./mosquitto/config:/mosquitto/config", "./mosquitto/data:/mosquitto/data"]

  jellyfin:
    image: jellyfin/jellyfin
    restart: always
    ports: ["8096:8096"]
    volumes: ["./jellyfin/config:/config", "./jellyfin/cache:/cache", "/volume1/media:/media"]
    devices: ["/dev/dri/renderD128:/dev/dri/renderD128"]

  rtsp-server:
    image: bluenviron/mediamtx
    restart: always
    ports: ["8554:8554"]
    volumes: ["./mediamtx:/config", "/volume1/camera_record:/recordings"]

  hermes-ai:
    image: lmsys/fastchat
    restart: always
    ports: ["8080:8080"]
    volumes: ["/volume1/photo:/nas_photo", "/volume1/media:/nas_media"]

  n8n:
    image: n8nio/n8n
    restart: always
    ports: ["5678:5678"]
    volumes: ["./n8n:/root/.n8n"]
    environment: [N8N_SECURE_COOKIE=false]

  samba-game:
    image: dperson/samba
    restart: always
    ports: ["139:139", "445:445"]
    volumes: ["/volume1/game_library:/game"]
    environment: [TZ=Asia/Shanghai]
    command: >
      -u "gameuser;GamePass123"
      -s "GameLibrary;/game;yes;no;no;gameuser;gameuser;NAS全屋游戏库"

docker-compose up -d
services:
  frigate:
    image: ghcr.io/blakeblackshear/frigate
    container_name: frigate
    restart: unless-stopped
    shm_size: "256mb"
    devices:
      - /dev/bus/usb:/dev/bus/usb
      - /dev/dri:/dev/dri
    volumes:
      - ./config:/config
      - ./media:/media/frigate
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 5000:5000
      - 8971:8971
      - 8554:8554
      - 8555:8555/tcp
    environment:
      TZ: Asia/Shanghai
      FRIGATE_RTSP_PASSWORD: pwd
services:
  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mosquitto
    restart: unless-stopped
    volumes:
      - ./config/mosquitto/config:/mosquitto/config
      - ./config/mosquitto/data:/mosquitto/data
    ports:
      - "1883:1883"
    networks:
      - aihub

  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    environment:
      - OLLAMA_HOST=0.0.0.0
      - OLLAMA_KEEP_ALIVE=24h
      - OLLAMA_NUM_PARALLEL=1
      - OLLAMA_MAX_LOADED_MODELS=1
      - TZ=Asia/Shanghai
    volumes:
      - ./config/ollama:/root/.ollama
    ports:
      - "11434:11434"
    networks:
      - aihub

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: unless-stopped
    depends_on:
      - ollama
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - FRIGATE_URL=http://frigate:5000
      - TZ=Asia/Shanghai
    volumes:
      - ./config/open-webui:/app/backend/data
    ports:
      - "3000:8080"
    networks:
      - aihub

  frigate:
    image: ghcr.io/blakeblackshear/frigate:stable
    container_name: frigate
    restart: unless-stopped
    stop_grace_period: 30s
    shm_size: "512mb"
    depends_on:
      - mosquitto
    devices:
      - /dev/dri/renderD128:/dev/dri/renderD128
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./config/frigate:/config
      - /vol2/1000/camera:/media/frigate
      - type: tmpfs
        target: /tmp/cache
        tmpfs:
          size: 1000000000
    ports:
      - "8971:8971"
      - "127.0.0.1:5000:5000"
      - "8554:8554"
      - "8555:8555/tcp"
      - "8555:8555/udp"
    environment:
      - FRIGATE_RTSP_PASSWORD=摄像头密码
      - TZ=Asia/Shanghai
    networks:
      - aihub

  double-take:
    image: ghcr.io/jakowenko/double-take:latest
    container_name: double-take
    restart: unless-stopped
    depends_on:
      - mosquitto
      - frigate
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./config/double-take:/.storage
    ports:
      - "3001:3000"
    networks:
      - aihub

  care-bridge:
    build: ./bridge
    container_name: care-bridge
    restart: unless-stopped
    depends_on:
      - mosquitto
      - ollama
      - frigate
    environment:
      - TZ=Asia/Shanghai
      - MQTT_HOST=mosquitto
      - MQTT_PORT=1883
      - MQTT_USER=mqtt
      - MQTT_PASSWORD=mqtt123
      - FRIGATE_URL=http://frigate:5000
      - OLLAMA_URL=http://ollama:11434
      - OLLAMA_MODEL=qwen3-vl:4b
      - CARE_CAMERA=keting
      - CARE_NIGHT_START=22
      - CARE_NIGHT_END=6
      - STILL_ALERT_SECONDS=600
      - DOWN_HOLD_SECONDS=20
      - DAILY_REPORT_HOUR=20
      - TELEGRAM_BOT_TOKEN=填你的机器人Token
      - TELEGRAM_CHAT_ID=填你的ChatID
      - DB_PATH=/data/care.db
      - LOG_PATH=/data/care.log
    volumes:
      - ./data:/data
    networks:
      - aihub

networks:
  aihub:
    driver: bridge

cd /vol2/1000/aihub
docker compose up -d --build
mqtt:
  host: mosquitto
  username: mqtt
  password: mqtt123
  topics:
    frigate: frigate/events
    matches: double-take/matches
    cameras: double-take/cameras

frigate:
  url: http://frigate:5000
  update_sub_labels: false
  stop_on_match: true
  min_area: 5000
  labels:
    - person
  attempts:
    latest: 10
    snapshot: 10
    mqtt: true
    delay: 0
  image:
    height: 500

detect:
  match:
    save: true
    confidence: 60
    min_area: 10000
  unknown:
    save: true
    confidence: 40

detectors:
  compreface:
    url: http://192.168.31.200:8000/api/v1
    key: 上一步复制的APIKey
    timeout: 15
    det_prob_threshold: 0.8
    opencv_face_required: false
    cameras:
      - menkou
      - keting
"""
title: 家庭安防查询
author: 辣丝刀客
version: 0.1.0
license: MIT
requirements: requests
"""

import os
from datetime import datetime, timedelta

import requests


FRIGATE_URL = os.getenv("FRIGATE_URL", "http://frigate:5000")


class Tools:

    def __init__(self):
        self.citation = False

    def get_recent_person_events(self, camera: str = "", limit: int = 10) -> str:
        """
        查询最近的人形监控事件。camera 参数传摄像头名称(如 menkou、keting),
        留空表示查全部摄像头。limit 是返回条数,默认 10。
        """
        params = {"limit": int(limit)}
        if camera:
            params["camera"] = camera
        resp = requests.get(f"{FRIGATE_URL}/api/events", params=params, timeout=20)
        resp.raise_for_status()
        events = resp.json()
        if not events:
            return "最近没有任何人形事件。"

        lines = []
        for item in events:
            name = item.get("sub_label") or "未识别"
            when = datetime.fromtimestamp(item.get("start_time", 0)).strftime("%m-%d %H:%M")
            lines.append(
                f"{when} {item.get('camera')} 检测到 {item.get('label')},人脸:{name}"
            )
        return "\n".join(lines)

    def request_daily_summary(self, hours: int = 12) -> str:
        """
        让 Frigate 用本地大模型给最近几小时的活动生成一份中文摘要报告。
        hours 是往前推的小时数,默认 12。
        """
        end_ts = int(datetime.now().timestamp())
        start_ts = int((datetime.now() - timedelta(hours=int(hours))).timestamp())
        resp = requests.post(
            f"{FRIGATE_URL}/api/review/summarize/start/{start_ts}/end/{end_ts}",
            timeout=60,
        )
        return f"已请求生成摘要,HTTP {resp.status_code}:{resp.text[:300]}"
services:
  go2rtc:
    image: alexxit/go2rtc:latest
    container_name: go2rtc
    restart: unless-stopped
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - /vol4/1000/go2rtc:/config
    ports:
      - "1984:1984"
    networks:
      - nvr

  frigate:
    image: ghcr.io/blakeblackshear/frigate
    container_name: frigate
    restart: unless-stopped
    shm_size: "256mb"
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - /vol4/1000/frigate:/config
      - /vol4/1000/nvr:/media/frigate
      - /dev/dri:/dev/dri
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "8971:8971"
    networks:
      - nvr

networks:
  nvr:
    driver: bridge

智能家居与语音 4 段

version: '3.8'
services:
  migpt:
    image: idootop/mi-gpt:latest
    container_name: migpt
    restart: always
    ports:
      - "36592:36592"
    volumes:
      - ./config:/app/config
      - ./data:/app/data
    environment:
      - MIGPT_AI_PLATFORM=doubao
      - "MIGPT_DOUBAO_API_KEY=\
        sk-你的火山方舟密钥"
      - "MIGPT_DOUBAO_MODEL=\
        ep-20251231181234-xxxx"
      - MIGPT_MI_USER_ID=你的小米UID
      - MIGPT_MI_DID=小爱音箱did
      - TZ=Asia/Shanghai
version: "3.8"

services:
  # NAS 本地豆包推理服务
  local-doubao:
    image: your-local-doubao:latest
    container_name: local-doubao
    restart: always
    ports:
      - "0.0.0.0:8080:8080"
    environment:
      # 模型目录,按实际路径改
      MODEL_PATH: "/models/doubao-offline"
      ENABLE_CLOUD_UPLOAD: "false"
      ENABLE_EXTERNAL_API: "false"
      INTRANET_ONLY: "true"
      LOG_LEVEL: "info"
    volumes:
      - ./doubao-models:/models
      - ./doubao-config:/config
      - ./doubao-logs:/logs
    networks:
      - lan-bridge
    deploy:
      resources:
        reservations:
          memory: 8G
    cap_drop:
      - NET_RAW

  # 米家本地桥接(内网控制)
  miot-local-bridge:
    image: miot-local-bridge:latest
    container_name: miot-local-bridge
    restart: always
    ports:
      - "0.0.0.0:8090:8090"
    environment:
      # 这里换成你的米家 TOKEN
      MIOT_TOKEN: "your-miot-token"
      DISABLE_MI_CLOUD: "true"
      SCAN_LAN_ONLY: "true"
      DEVICE_DB_PATH: "/db/devices.db"
    volumes:
      - ./miot-db:/db
      - ./miot-config:/config
    networks:
      - lan-bridge
    depends_on:
      - local-doubao

networks:
  lan-bridge:
    driver: bridge
services:
  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:stable
    container_name: homeassistant
    restart: unless-stopped
    privileged: true            # 访问 USB 设备(Zigbee 网关等)必需
    network_mode: host          # 用 host 模式才能广播发现家里的设备
    volumes:
      - ./config:/config
    environment:
      TZ: Asia/Shanghai
services:
  homeassistant:
    image: ${HA}:stable
    container_name: homeassistant
    restart: unless-stopped
    network_mode: host
    privileged: true
    volumes:
      - ./config:/config
      - /run/dbus:/run/dbus:ro
      - /etc/localtime:/etc/localtime:ro
    env_file: .env
需要的变量(.env)
HA=ghcr.io/home-assistant/home-assistant

游戏与掌机 6 段

version: "3.8"
services:
steam-headless:
image: steamheadless/steam-headless:latest
container_name: Steam-Headless
restart: always
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- VNC_PASSWD=123456
volumes:
- /mnt/SteamLibrary:/home/steam/.steam/steamapps
ports:
version: "3.8"
services:
  samba-game:
    image: dperson/samba
    container_name: samba-game
    restart: always
    ports:
      - "139:139"
      - "445:445"
    volumes:
      - /volume1/game_library:/game
    environment:
      - TZ=Asia/Shanghai
    command: >
      -u "gameuser;GamePass123"
      -s "GameLibrary;/game;yes;no;no;gameuser;gameuser;NAS全屋游戏库"
services:
  sunshine:
    image: lscr.io/linuxserver/sunshine:latest
    container_name: sunshine
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /vol2/1000/docker/sunshine/config:/config
    ports:
      - 47984-48010:47984-48010/tcp
      - 47984-48010:47984-48010/udp
      - 47989:47989/tcp
      - 47990:47990/tcp
    devices:
      - /dev/dri:/dev/dri
    restart: unless-stopped
services:
  samba:
    image: dperson/samba:latest
    container_name: samba
    restart: unless-stopped
    ports:
      - "445:445"
      - "139:139"
    environment:
      - TZ=Asia/Shanghai
      - USERID=1000
      - GROUPID=1000
    volumes:
      - /vol2/1000/games:/games
    command:
      - -u
      - "handheld;改成你的强密码"
      - -s
      - "games;/games;yes;no;no;handheld"
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:latest
    container_name: wg-easy
    environment:
      - LANG=zh
      - WG_HOST=你的域名或公网IP
      - WG_PORT=51820
      - WG_DEFAULT_ADDRESS=10.8.0.x
      - WG_DEFAULT_DNS=223.5.5.5
      - "WG_ALLOWED_IPS=\
        192.168.1.0/24,10.8.0.0/24"
      - WG_PERSISTENT_KEEPALIVE=25
      - "PASSWORD_HASH=\
        把上一步生成的哈希粘这里"
    volumes:
      - "/vol2/1000/docker/\
        wg-easy:/etc/wireguard"
    ports:
      - 51820:51820/udp
      - 51821:51821/tcp
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv4.ip_forward=1
    restart: unless-stopped
services:
  samba:
    image: dockurr/samba:latest
    container_name: samba
    environment:
      - NAME=games
      - USER=nasuser
      - PASS=改成你的强密码
      - SHARE=/games
      - READONLY=false
      - PERMISSIONS=true
    volumes:
      - /vol2/1000/games:/games
    ports:
      - "445:445"
      - "139:139"
    restart: unless-stopped

  nfs:
    image: itsthenetwork/nfs-server-alpine
    container_name: nfs
    privileged: true
    environment:
      - SHARED_DIR=/games
      - PERMITTED=192.168.1.0/24
      - SYNC=true
    volumes:
      - /vol2/1000/games:/games
    ports:
      - "2049:2049"
    restart: unless-stopped

  syncthing:
    image: syncthing/syncthing:latest
    container_name: syncthing
    hostname: game-nas
    environment:
      - PUID=1000
      - PGID=1001
      - TZ=Asia/Shanghai
    volumes:
      - ./st:/var/syncthing
      - /vol2/1000/games/saves:/data/saves
    ports:
      - "8384:8384"
      - "22000:22000/tcp"
      - "22000:22000/udp"
      - "21027:21027/udp"
    restart: unless-stopped

  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale
    hostname: game-nas
    environment:
      - TS_AUTHKEY=改成你的密钥
      - TS_STATE_DIR=/var/lib/tailscale
    volumes:
      - ./ts:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - NET_ADMIN
      - NET_RAW
    restart: unless-stopped

cd /vol2/1000/docker/games
docker compose up -d
docker compose logs -f samba

远程访问与穿透 1 段

services:
  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale
    hostname: nas-gateway
    environment:
      - "TS_AUTHKEY=\
        tskey-auth-换成你自己的密钥"
      - TS_STATE_DIR=/var/lib/tailscale
      - "TS_EXTRA_ARGS=\
        --advertise-routes=\
        192.168.1.0/24"
      - TS_AUTH_ONCE=true
    volumes:
      - "/vol2/1000/docker/tailscale/\
        state:/var/lib/tailscale"
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - NET_ADMIN
      - NET_RAW
    network_mode: host
    restart: unless-stopped

备份与迁移 3 段

docker-compose 完整配置,改两个地方就能用:
services:
  syncthing:
    image: lscr.io/linuxserver/syncthing:latest
    container_name: syncthing
    hostname: syncthing
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /vol2/1000/docker/syncthing/config:/config
      - /vol2/1000/docker/syncthing/saves:/data
    ports:
      - 8384:8384          # Web 管理界面
      - 22000:22000/tcp    # 同步端口
      - 22000:22000/udp
      - 21027:21027/udp    # 本地发现
    restart: unless-stopped
version: "3.8"

services:
  emu-sync:
    image: syncthing/syncthing:1.27
    container_name: emu-sync
    restart: always
    environment:
      # 改成 NAS 上的实际用户 ID
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    ports:
      # 8384 是管理界面端口
      - "8384:8384"
      - "22000:22000/tcp"
      - "22000:22000/udp"
      - "21027:21027/udp"
    volumes:
      # 配置目录(长格式,避免行超宽)
      - type: bind
        source: ./syncthing/config
        target: /var/syncthing/config
      - /vol1/1000/emu-saves:/emu-saves
services:
  syncthing:
    image: syncthing/syncthing:latest
    container_name: syncthing
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes:
      - /vol4/1000/sync:/var/syncthing
      - /vol4/1000/album:/data/photos
    ports:
      - "8384:8384"
      - "22000:22000/tcp"
      - "22000:22000/udp"
      - "21027:21027/udp"
    networks:
      - home

networks:
  home:
    driver: bridge
mkdir -p /vol4/1000/sync
mkdir -p /vol4/1000/album
docker compose up -d

AI 与自动化 9 段

services:
  postgres:
    image: postgres:17-alpine
    container_name: mumuainovel-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: mumuai_novel
      POSTGRES_USER: mumuai
      POSTGRES_PASSWORD: "改成你的强密码"
      TZ: Asia/Shanghai
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
    networks:
      - mumuainovel-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mumuai -d mumuai_novel"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
  mumuainovel:
    image: mumujie/mumuainovel:latest
    container_name: mumuainovel
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "8832:8000"
    volumes:
      - ./data:/app/data
      - ./logs:/app/logs
      - ./.env:/app/.env
    networks:
      - mumuainovel-net
    environment:
      TZ: Asia/Shanghai
      DATABASE_HOST: postgres
      DATABASE_PORT: "5432"
      DATABASE_NAME: mumuai_novel
      DATABASE_USER: mumuai
      DATABASE_PASSWORD: "改成你的强密码"
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
networks:
  mumuainovel-net:
    driver: bridge
version: "3.8"

services:
  dsh:
    image: node:24-alpine
    container_name: deepseek-harness
    restart: always
    ports:
      - "3080:3080"
    environment:
      - TZ=Asia/Shanghai
      - DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
      - DSH_HOST=0.0.0.0
      - DSH_PORT=3080
    volumes:
      - ./config:/root/.dsh
      - ./workspace:/app/workspace
    command: >
      sh -c "npm install -g @deepseek-ai/dsh && dsh web --host 0.0.0.0 --port 3080"
version: "3.8"

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ./ollama:/root/.ollama
    environment:
      - OLLAMA_HOST=0.0.0.0
      - OLLAMA_NUM_PARALLEL=4
      - OLLAMA_MAX_LOADED_MODELS=2
    networks:
      - arr_net

networks:
  arr_net:
    external: true
open-webui:
    image: ghcr.io/open-webui/open-webui:latest
    container_name: open-webui
    restart: unless-stopped
    ports:
      - "3000:8080"
    volumes:
      - ./open-webui:/app/backend/data
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
    depends_on:
      - ollama
    networks:
      - arr_net

http://NAS-IP:3000
version: "3.8"

services:
  hermes:
    image: nousresearch/hermes-agent:latest
    container_name: hermes
    restart: unless-stopped
    command: gateway run
    ports:
      - "26429:26429"
    volumes:
      - ./hermes_data:/opt/data
      - /etc/localtime:/etc/localtime:ro
    environment:
      # 本地Ollama模型对接
      - MODEL_PROVIDER=ollama
      - MODEL_BASE_URL=http://ollama:11434/v1
      - MODEL_NAME=qwen3:8b
      - MODEL_API_KEY=dummy

      # AI自我训练/自我进化开关
      - SELF_IMPROVEMENT_ENABLE=true
      - SELF_IMPROVEMENT_MAX_PATCHES=3
      - SELF_IMPROVEMENT_ALLOW_FILE_WRITE=true
      - SELF_IMPROVEMENT_ALLOW_COMMAND=false

      # 微信安全策略(防风控、防乱入)
      - WEIXIN_DM_POLICY=pairing_approval
      - WEIXIN_GROUP_POLICY=disable
    deploy:
      resources:
        limits:
          memory: 6G
          cpus: "2.0"
    networks:
      - arr_net

networks:
  arr_net:
    external: true

docker compose up -d
http://你的NAS-IP:26429
services:
  hermes:
    image: nousresearch/hermes:latest
    ports:
      - "18642:18642"
      - "19119:19119"
    env_file:
      - ./.env
    volumes:
      - ./hermes-data:/root/.hermes
    restart: unless-stopped
version: "3.8"
services:
  服务自定义名:
    image: 镜像地址
    container_name: 容器名
    ports:
      - "主机端口:容器端口"
    volumes:
      - /你的NAS本地路径:/容器内路径
    environment:
      - TZ=Asia/Shanghai
services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - TZ=Asia/Shanghai
      - GENERIC_TIMEZONE=Asia/Shanghai
      - N8N_HOST=n8n.example.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - WEBHOOK_URL=http://n8n.example.com/
      - N8N_ENCRYPTION_KEY=change_me_random_32_char_string
    volumes:
      - ./n8n_data:/home/node/.n8n
services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    ports:
      - 11434:11434
    volumes:
      - ./models:/root/.ollama

效率工具与全家桶 22 段

services:
  libretv:
    image: ghcr.io/librespark/libretv
    container_name: libretv
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      # 访问密码(必填,改成自己的)
      - PASSWORD=change-me
      # 会话签名密钥(多实例必设)
      - PROXY_SECRET=change-me-too
      # 自建内网IPTV源才开
      # - LIVE_ALLOW_PRIVATE=1
      # 上游超时(毫秒)
      # - REQUEST_TIMEOUT=8000
      # 每源抓取页数
      # - SEARCH_MAX_PAGES=5
      # 排错日志
      # - DEBUG=1
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.你的域名.com"   # 有域名就填,自用可留空
      SIGNUPS_ALLOWED: "true"               # 注册完一个账号后记得改 false
      ADMIN_TOKEN: "改成你的一长串随机密码"    # 开启 /admin 后台管理
      WEBSOCKET_ENABLED: "true"            # 开启即可在浏览器自动填充
    volumes:
      - ./vw-data:/data
    ports:
      - 80:80
services:
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    ports:
      - 9443:9443     # HTTPS 管理界面,用浏览器打开会先要你设置管理员密码
      - 8000:8000     # Agent 端口(可选)
    volumes:
      - ./data:/data
      - /var/run/docker.sock:/var/run/docker.sock
services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    environment:
      USER_UID: 1000
      USER_GID: 1000
      GITEA__server__DOMAIN: "NAS-IP:3000"
      GITEA__server__ROOT_URL: "http://NAS-IP:3000/"
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - 3000:3000
services:
  nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    restart: unless-stopped
    ports:
      - 8080:80
    volumes:
      - ./html:/var/www/html
      - ./data:/var/www/html/data
    environment:
      - NEXTCLOUD_ADMIN_USER=admin
      - NEXTCLOUD_ADMIN_PASSWORD=设置你的强密码
      - NEXTCLOUD_TRUSTED_DOMAINS=NAS-IP

  db:
    image: postgres:16-alpine
    container_name: nextcloud-db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=nextcloud
      - POSTGRES_USER=nextcloud
      - POSTGRES_PASSWORD=给数据库设个独立密码
    volumes:
      - ./db:/var/lib/postgresql/data
services:
  linkwarden:
    image: ${LW}:latest
    container_name: linkwarden
    restart: unless-stopped
    env_file: .env
    ports:
      - 3000:3000
    volumes:
      - ./data:/data/data
    depends_on:
      - db
      - meili

  db:
    image: postgres:16-alpine
    container_name: lw-db
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./pgdata:/var/lib/postgresql/data

  meili:
    image: getmeili/meilisearch:v1.11
    container_name: lw-meili
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./meili:/meili_data
需要的变量(.env)
LW=ghcr.io/linkwarden/linkwarden
services:
  stirling-pdf:
    image: frooodle/s-pdf:latest
    container_name: stirling-pdf
    restart: unless-stopped
    ports:
      - 8181:8080
    environment:
      DOCKER_ENABLE_SECURITY: "false"
      INSTALL_BOOK_AND_ADVANCED: "true"
      LANGS: zh_CN
      TZ: Asia/Shanghai
    volumes:
      - ./configs:/configs
      - ./logs:/logs
      - ./pipeline:/pipeline
      - ./customFiles:/customFiles
services:
  mealie:
    image: ghcr.io/mealie-recipes/mealie
    container_name: mealie
    restart: unless-stopped
    ports:
      - 9925:9000
    environment:
      ALLOW_SIGNUP: "true"
      BASE_URL: http://10.0.0.2:9925
      TZ: Asia/Shanghai
    volumes:
      - ./data:/app/data
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    container_name: mealie-db
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: pw
      POSTGRES_USER: mealie
      POSTGRES_DB: mealie
      TZ: Asia/Shanghai
    volumes:
      - ./pgdata:/var/lib/postgresql/data
services:
  calibre-web:
    image: linuxserver/calibre-web
    container_name: calibre-web
    restart: unless-stopped
    ports:
      - 8083:8083
    environment:
      PUID: 1000
      PGID: 1000
      TZ: Asia/Shanghai
    volumes:
      - ./config:/config
      - ./books:/books
services:
  backrest:
    image: ghcr.io/garethgeorge/backrest
    container_name: backrest
    restart: unless-stopped
    ports:
      - 9898:9898
    environment:
      BACKREST_DATA: /data
      BACKREST_CONFIG: /data/config.json
      XDG_CACHE_HOME: /cache
      TZ: Asia/Shanghai
    volumes:
      - ./data:/data
      - ./cache:/cache
      - /vol1/1000:/userdata:ro
services:
  docmost:
    image: docmost/docmost:latest
    container_name: docmost
    restart: unless-stopped
    env_file: .env
    ports:
      - 3080:3000
    volumes:
      - ./data:/app/data/storage
    depends_on:
      - db
      - redis

  db:
    image: postgres:16-alpine
    container_name: docmost-db
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    container_name: docmost-redis
    restart: unless-stopped
    volumes:
      - ./redis:/data
services:
  webserver:
    image: ${APP}
    container_name: paperless
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      broker:
        condition: service_started
      gotenberg:
        condition: service_started
      tika:
        condition: service_started
    ports:
      - "8000:8000"
    volumes:
      - ${DATA}/data:${PB}/data
      - ${DATA}/media:${PB}/media
      - ${CONSUME}:${PB}/consume
      - ${DATA}/export:${PB}/export
    environment:
      - TZ
      - PUID
      - PGID
      - PAPERLESS_DBHOST
      - PAPERLESS_DBPORT
      - PAPERLESS_DBNAME
      - PAPERLESS_DBUSER
      - PAPERLESS_DBPASS
      - PAPERLESS_REDIS
      - PAPERLESS_SECRET_KEY
      - PAPERLESS_TIME_ZONE
      - PAPERLESS_OCR_LANGUAGE
      - PAPERLESS_URL
      - PAPERLESS_TIKA_ENABLED
      - PAPERLESS_TIKA_ENDPOINT
      - PAPERLESS_TIKA_GOTENBERG_ENDPOINT
    networks:
      - pl

  db:
    image: ${DB}
    container_name: paperless_db
    restart: unless-stopped
    environment:
      - TZ
      - POSTGRES_DB=paperless
      - POSTGRES_USER=paperless
      - POSTGRES_PASSWORD=${DBPASS}
    volumes:
      - ${DATA}/pgdata:${PGD}
    healthcheck:
      test: pg_isready -U paperless
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - pl

  broker:
    image: ${CACHE}
    container_name: paperless_redis
    restart: unless-stopped
    volumes:
      - ${DATA}/redis:/data
    networks:
      - pl

  gotenberg:
    image: ${GOT}
    container_name: paperless_gotenberg
    restart: unless-stopped
    networks:
      - pl

  tika:
    image: ${TIKA}
    container_name: paperless_tika
    restart: unless-stopped
    networks:
      - pl

networks:
  pl:
    driver: bridge
需要的变量(.env)
APP=${REG}/paperless-ngx:${TAG}
CACHE=docker.io/redis:7
CONSUME=/vol2/1000/scan
DATA=/vol2/1000/paperless
DB=docker.io/postgres:16
DBPASS=改成你的强数据库密码
GOT=docker.io/gotenberg/gotenberg:8
PB=/usr/src/paperless
PGD=/var/lib/postgresql/data
REG=ghcr.io/paperless-ngx
TAG=2.14.5
TIKA=docker.io/apache/tika:latest
services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - 3001:3001
    volumes:
      - ./data:/app/data
services:
  vaultwarden:
    image: vaultwarden/server
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      # 先开着,注册完改成 false
      - SIGNUPS_ALLOWED=true
      # 换成你的访问地址(必须 https)
      - DOMAIN=https://v.abc.cn
    ports:
      - 8081:80
    volumes:
      - ./data:/data
services:
  gitea:
    image: gitea/gitea:1
    container_name: gitea
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__server__DOMAIN=10.0.0.5
      - GITEA__server__SSH_PORT=2222
    ports:
      - 3002:3000
      - 2222:22
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
services:
  homepage:
    image: ghcr.io/gethomepage/homepage
    container_name: homepage
    restart: unless-stopped
    ports:
      - 3000:3000
    environment:
      - HOMEPAGE_ALLOWED_HOSTS=nas.lan
    volumes:
      - ./config:/app/config
services:
  dozzle:
    image: amir20/dozzle
    container_name: dozzle
    restart: unless-stopped
    ports:
      - 8083:8080
    volumes:
      - type: bind
        source: /var/run/docker.sock
        target: /var/run/docker.sock
        read_only: true
services:
  stirling-pdf:
    image: stirlingtools/stirling-pdf
    container_name: stirling-pdf
    restart: unless-stopped
    ports:
      - 8084:8080
    environment:
      - DOCKER_ENABLE_SECURITY=false
      - LANGS=zh_CN
    volumes:
      - ./configs:/configs
      - ./logs:/logs
services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    command: --label-enable --cleanup
    volumes:
      - type: bind
        source: /var/run/docker.sock
        target: /var/run/docker.sock
        read_only: true
services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - 3001:3001
    volumes:
      - ./data:/app/data

  vaultwarden:
    image: vaultwarden/server
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      # 先开着,注册完改成 false
      - SIGNUPS_ALLOWED=true
      # 换成你的访问地址(必须 https)
      - DOMAIN=https://v.abc.cn
    ports:
      - 8081:80
    volumes:
      - ./data:/data

  gitea:
    image: gitea/gitea:1
    container_name: gitea
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__server__DOMAIN=10.0.0.5
      - GITEA__server__SSH_PORT=2222
    ports:
      - 3002:3000
      - 2222:22
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

  navidrome:
    image: deluan/navidrome
    container_name: navidrome
    restart: unless-stopped
    ports:
      - 4533:4533
    environment:
      - ND_MUSICFOLDER=/music
      - ND_SCANSCHEDULE=1h
    volumes:
      - ./data:/data
      - /vol1/1000/music:/music:ro

  homepage:
    image: ghcr.io/gethomepage/homepage
    container_name: homepage
    restart: unless-stopped
    ports:
      - 3000:3000
    environment:
      - HOMEPAGE_ALLOWED_HOSTS=nas.lan
    volumes:
      - ./config:/app/config

  dozzle:
    image: amir20/dozzle
    container_name: dozzle
    restart: unless-stopped
    ports:
      - 8083:8080
    volumes:
      - type: bind
        source: /var/run/docker.sock
        target: /var/run/docker.sock
        read_only: true

  stirling-pdf:
    image: stirlingtools/stirling-pdf
    container_name: stirling-pdf
    restart: unless-stopped
    ports:
      - 8084:8080
    environment:
      - DOCKER_ENABLE_SECURITY=false
      - LANGS=zh_CN
    volumes:
      - ./configs:/configs
      - ./logs:/logs

  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    command: --label-enable --cleanup
    volumes:
      - type: bind
        source: /var/run/docker.sock
        target: /var/run/docker.sock
        read_only: true
paperless:
    image: ${PAP}
    container_name: paperless
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - PAPERLESS_REDIS=${PLR}
      - PAPERLESS_URL=https://p.abc.cn
    volumes:
      - ${PPD}:/usr/src/paperless/data
      - ${PPM}:/usr/src/paperless/media
      - ${PPC}:/usr/src/paperless/consume
    ports:
      - "8000:8000"
    depends_on:
      - plredis
    restart: unless-stopped

  plredis:
    image: redis:7
    container_name: plredis
    restart: unless-stopped
需要的变量(.env)
PUID=1000
PGID=1000
TZ=Asia/Shanghai
DATA=/vol1/1000/data
GH=ghcr.io
PAP=${GH}/${PNX}/${PNX}:latest
PLR=redis://plredis:6379
PNX=paperless-ngx
PPC=${DATA}/scans
PPD=${ROOT}/paperless/data
PPM=${ROOT}/paperless/media
ROOT=/vol1/1000/docker
vaultwarden:
    image: vaultwarden/server
    container_name: vaultwarden
    environment:
      - SIGNUPS_ALLOWED=true
      - DOMAIN=https://v.abc.cn
      - TZ=${TZ}
    volumes:
      - ${VWD}:/data
    ports:
      - "8081:80"
    restart: unless-stopped

  filebrowser:
    image: filebrowser/filebrowser
    container_name: filebrowser
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
    volumes:
      - ${DATA}/srv:/srv
      - ${FBD}:/database
      - ${FBC}:/config
    ports:
      - "8082:80"
    restart: unless-stopped
需要的变量(.env)
PUID=1000
PGID=1000
TZ=Asia/Shanghai
DATA=/vol1/1000/data
FBC=${ROOT}/filebrowser/cfg
FBD=${ROOT}/filebrowser/db
ROOT=/vol1/1000/docker
VWD=${ROOT}/vaultwarden

其他 3 段

services:
  app-db:
    image: postgres:16-alpine
    container_name: app-db
    environment:
      # 含 # 和 @ 必须加引号
      POSTGRES_PASSWORD: "a#b@c2026"
      POSTGRES_USER: "app"
      POSTGRES_DB: "appdb"
      # 数字也建议加引号
      PUID: "1000"
      TZ: "Asia/Shanghai"
      # 整段 URL 一起加引号
      APP_BASE_URL: "https://nas.a.cn"
# 确认容器里真实拿到的值
docker inspect app-db \
  --format '{{json .Config.Env}}'
services:
  app-db:
    image: postgres:16-alpine
    container_name: app-db
    environment:
      POSTGRES_PASSWORD: "a#b@c2026"
      POSTGRES_USER: "app"
      POSTGRES_DB: "appdb"
    healthcheck:
      test:
        - CMD-SHELL
        - pg_isready -U app -d appdb
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  app:
    image: ghcr.io/hotio/demo-app
    container_name: demo-app
    depends_on:
      app-db:
        condition: service_healthy
    restart: unless-stopped
services:
  nginx:
    image: nginx:alpine
    container_name: nas-nginx
    ports:
      - "443:443"
    extra_hosts:
      # 让容器能访问宿主机
      - host.docker.internal:host-gateway