Skip to content

家庭服务AllInDocker

约 4595 字大约 15 分钟

docker

2025-07-18

前言

最近在海鲜市场淘了个矿渣,配置和性能都比我那软路由高多了,价钱却便宜了很多,虽然也还是 arm 设备,但是硬件可以支持做更多事,于是将 r2s 上的一些服务尽可能转移过来,用 docker compose 部署。

1. 规划分类

计划分 3 个 compose.yaml 文件

  1. hodev: home➕develop 的缩写,内置开发相关服务。
  2. homeia: home➕media 的缩写,内置媒体相关服务。
  3. honet: home➕network 的缩写,内置网络相关的服务。

部署原则如下

  1. 对于提供支撑类型的服务,稳定性高于新功能,因此指定版本号。
  2. 对于提供消费类型的服务,新功能优先,因此使用latest版本号,并且使用 watchtower 监听自动更新。
  3. 目录挂载可以使用数据卷目录绑定,但数据卷的方式只能映射目录,目录绑定的方式还可以绑定文件,这里个人偏好是目录绑定,方便管理。但要注意,目录绑定时宿主机上的目录会自动创建,但文件绑定时必须先创建好文件,否则会被当成目录名处理。当服务启动时,数据卷和绑定的目录中就会自动填入内容。
  4. 手动创建网络或数据卷。
  5. 个人暂时用不到的服务用了ignore忽略。
  6. 公共的环境变量放在.env文件中,服务个性化变量放在各服务命名的*.env文件中。
创建网络
docker network create --driver bridge home

2. 部署前置条件

准备一个非域名,因为有些服务必须要 https 域名访问。
可以是仅内网域名(如: dev.lan),也可上DigitalPlat注册(2个免费域名且可托管至 Cloudflare),或者上Duck DNS免费注册至多5个子域名。

域名区别:

  1. 内网域名:优点是随意命名,缺点是客户端设备需要要导入根证书并信任。
  2. 公网域名:优点是不需要设备导入证书,缺点是不一定能注册到心意的域名。

个人推荐公网非子域名

3. hodev

文件结构和内容

hodev

compose.yaml

gitea.env

mysql.env

gitea

data

...

dbgate

data

...

mysql

data

...

my.cnf

redis

data

...

conf

redis.conf

.env

hodev/compose.yaml
services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    ports: [3306:3306]
    networks: [home]
    env_file: [mysql.env]
    restart: unless-stopped
    volumes:
      - ./mysql/data:/var/lib/mysql # 数据目录
      - ./mysql/my.cnf:/etc/my.cnf # 配置文件

  redis:
    image: redis:8.0.3
    container_name: redis
    ports: [6379:6379]
    networks: [home]
    restart: unless-stopped
    volumes:
      # 数据目录
      - ./redis/data:/data
      - ./redis/conf:/usr/local/etc/redis
    command: redis-server /usr/local/etc/redis/redis.conf

  gitea:
    image: gitea/gitea:1.24.3
    container_name: gitea
    ports: [9300:3000, 22:22]
    networks: [home]
    env_file: [gitea.env]
    restart: unless-stopped
    user: root
    volumes:
      - ./gitea/data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

  dbgate:
    image: dbgate/dbgate:6.5.6
    container_name: dbgate
    ports: [3300:3000]
    networks: [home]
    restart: unless-stopped
    volumes:
      - ./dbgate/data:/root/.dbgate


networks:
  home:
    external: true
hodev/gitea.env
# echo $(id -u)
USER_UID=1000
# echo $(id -g)
USER_GID=1000
hodev/mysql.env
# 字符集
MYSQL_CHARACTER_SET_SERVER=utf8mb4
# 排序规则
MYSQL_COLLATION_SERVER=utf8mb4_unicode_ci
# root 账户密码
MYSQL_ROOT_PASSWORD=root
# 自动创建的数据库
MYSQL_DATABASE=dev
# 自动创建的非root用户
MYSQL_USER=mayee
# 自动创建的用户密码
MYSQL_PASSWORD=123456
hodev/gitea/data/...
hodev/dbgate/data/...
hodev/mysql/data/...
hodev/mysql/my.cnf
# 从容器中拷贝出来
docker cp mysql:/etc/my.cnf .
hodev/redis/data/...
hodev/redis/conf/redis.conf
# 监听地址
bind 0.0.0.0
# 关闭保护(已经有密码了)
protected-mode no
requirepass 123456
# 开启 AOF 持久化
appendonly yes
appendfsync everysec
# 内存管理
maxmemory 256mb
maxmemory-policy allkeys-lru
hodev/.env
TZ=Asia/Shanghai

3.1 mysql

直接部署,创建的用户开启远程登录

3.2 redis

直接部署即可。

3.3 gitea

直接部署即可,配置参考官方文档。对于 gitea 的 SSH 端口,我没有做麻烦的直通,直接修改了宿主机的 SSH 端口。

注意

更多设置在/data/gitea/conf/app.ini

3.4 dbgate

网页端的数据库客户端,直接部署即可。

4. homedia

文件结构和内容

homedia

compose.yaml

jellyfin.env

libretv.env

moontv.env

navidrome.env

openlist.env

qinglong.env

vaultwarden.env

watchtower.env

jellyfin

config

...

cache

...

moontv

config.json

navidrome

data

...

openlist

data

...

portainer

data

...

qinglong

data

...

vaultwarden

data

...

.env

homedia/compose.yaml
services:
  allinone:
    image: youshandefeiyang/allinone:latest
    container_name: allinone
    ports: [35455:35455]
    networks: [home]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true
    command: >
      -tv=true
      -aesKey=xxxxxxx
      -userid=789123
      -token=123456

  navidrome:
    image: deluan/navidrome:0.57.0
    container_name: navidrome
    ports: [4533:4533]
    networks: [home]
    env_file: [navidrome.env]
    restart: unless-stopped
    volumes:
      - ./navidrome/data:/data
      - /mnt/app/media/music:/music:ro

  openlist:
    image: openlistteam/openlist:v4.0.8
    container_name: openlist
    ports: [5244:5244]
    networks: [home]
    env_file: [openlist.env]
    restart: unless-stopped
    volumes:
      - ./openlist/data:/opt/openlist/data
      - /mnt/app/media:/mnt/media

  libretv:
    image: bestzwei/libretv:latest
    container_name: libretv
    ports: [8899:8080]
    networks: [home]
    env_file: [libretv.env]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true

  moontv:
    image: ghcr.io/senshinya/moontv:latest
    container_name: moontv
    ports: [8300:3000]
    networks: [home]
    env_file: [moontv.env]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true
    volumes:
      - ./moontv/config.json:/app/config.json:ro

  iptvnator:
    profiles: ['ignore']
    image: 4gray/iptvnator:latest
    container_name: iptvnator
    ports: [8480:80]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true

  # 5700
  qinglong:
    profiles: ['ignore']
    image: whyour/qinglong:2.19.2
    container_name: qinglong
    ports: [8300:3000]
    networks: [home]
    env_file: [qinglong.env]
    restart: unless-stopped
    volumes:
      - ./qinglong/data:/ql/data

  vaultwarden:
    image: vaultwarden/server:1.34.1
    container_name: vaultwarden
    ports: [8137:8137]
    networks: [home]
    env_file: [vaultwarden.env]
    restart: unless-stopped
    volumes:
      - ./vaultwarden/data:/data

  jellyfin:
    profiles: ['ignore']
    image: jellyfin/jellyfin:10.10.7
    container_name: jellyfin
    ports: [8096:8096]
    networks: [home]
    env_file: [jellyfin.env]
    user: 1000:1000 # 替换为实际的 UID:GID。也可删掉这行,将使用 root 用户
    restart: unless-stopped
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /usr/share/fonts:/usr/local/share/fonts/custom:ro # 系统字体库
      - /mnt/app/media:/media:ro # 挂载媒体目录
    extra_hosts:
      - host.docker.internal:host-gateway # 相当于在容器内的 hosts 中加这些行。在 host 模式下 docker 健康检查可能会用的

  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    networks: [home]
    env_file: [watchtower.env]
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      
  portainer:
    image: portainer/portainer-ce:2.31.3
    container_name: portainer
    ports: [9000:9000]
    networks: [home]
    restart: unless-stopped
    volumes:
      - ./portainer/data:/data
      - /var/run/docker.sock:/var/run/docker.sock


networks:
  home:
    external: true
homedia/jellyfin.env
JELLYFIN_PublishedServerUrl=https://meida.mayee.dpdns.org
homedia/libretv.env
# 公开部署设置密码,避免可能法律风险
PASSWORD=''
ADMINPASSWORD=''
homedia/moontv.env
# 管理员账号(后台访问: /admin)
USERNAME: admin
# 管理员密码
PASSWORD: admin
# 应用名称
SITE_NAME: Homovie
# 公告
ANNOUNCEMENT: 家庭影视
NEXT_PUBLIC_STORAGE_TYPE: redis
REDIS_URL: redis://:123456@redis:6379
# 禁止注册,但可以通过管理面版添加用户
NEXT_PUBLIC_ENABLE_REGISTER: false
# 每页条数
NEXT_PUBLIC_SEARCH_MAX_PAGE: 15
homedia/navidrome.env
# https://www.navidrome.org/docs/usage/configuration-options/#available-options
ND_DEFAULTLANGUAGE=zh-Hans
ND_DEFAULTTHEME=Spotify-ish
ND_SESSIONTIMEOUT=12h
ND_ENABLETRANSCODINGCONFIG=true
ND_ENABLEGRAVATAR=true
ND_COVERARTPRIORITY=embedded
ND_LASTFM_LANGUAGE=zh
ND_SEARCHFULLSTRING=true
ND_LASTFM_APIKEY=xxx
ND_LASTFM_SECRET=xxx
ND_SPOTIFY_ID=xxx
ND_SPOTIFY_SECRET=xxx
ND_SCANNER_SCHEDULE=0
ND_IMAGECACHESIZE=100MB
ND_TRANSCODINGCACHESIZE=100MB
ND_LOGLEVEL=warn
homedia/openlist.env
# echo $(id -u)
PUID=1000
# echo $(id -g)
PGID=1000
UMASK=022
homedia/qinglong.env
QlBaseUrl=/
homedia/vaultwarden.env
# 绑定域名
DOMAIN=https://vw.mayee.dpdns.org
# 内部启动端口
ROCKET_PORT=8137
# 禁止注册
SIGNUPS_ALLOWED=false
# 图片服务器地址
ICON_SERVICE=https://icons.bitwarden.net/{}/icon.png
homedia/watchtower.env
WATCHTOWER_LABEL_ENABLE=true
WATCHTOWER_ROLLING_RESTART=true
WATCHTOWER_CLEANUP=true
WATCHTOWER_POLL_INTERVAL=7200
homedia/jellyfin/config/...
homedia/jellyfin/cache/...
homedia/moontv/config.json
{
  "cache_time": 7200,
  "api_site": {
    "dyttzy": {
      "api": "http://caiji.dyttzyapi.com/api.php/provide/vod",
      "name": "电影天堂资源",
      "detail": "http://caiji.dyttzyapi.com"
    },
    "heimuer": {
      "api": "https://json.heimuer.xyz/api.php/provide/vod",
      "name": "黑木耳",
      "detail": "https://heimuer.tv"
    },
    "ruyi": {
      "api": "https://cj.rycjapi.com/api.php/provide/vod",
      "name": "如意资源"
    },
    "bfzy": {
      "api": "https://bfzyapi.com/api.php/provide/vod",
      "name": "暴风资源"
    },
    "tyyszy": {
      "api": "https://tyyszy.com/api.php/provide/vod",
      "name": "天涯资源"
    },
    "ffzy": {
      "api": "http://ffzy5.tv/api.php/provide/vod",
      "name": "非凡影视",
      "detail": "http://ffzy5.tv"
    },
    "zy360": {
      "api": "https://360zy.com/api.php/provide/vod",
      "name": "360资源"
    },
    "iqiyi": {
      "api": "https://www.iqiyizyapi.com/api.php/provide/vod",
      "name": "iqiyi资源"
    },
    "wolong": {
      "api": "https://wolongzyw.com/api.php/provide/vod",
      "name": "卧龙资源"
    },
    "jisu": {
      "api": "https://jszyapi.com/api.php/provide/vod",
      "name": "极速资源",
      "detail": "https://jszyapi.com"
    },
    "dbzy": {
      "api": "https://dbzy.tv/api.php/provide/vod",
      "name": "豆瓣资源"
    },
    "mozhua": {
      "api": "https://mozhuazy.com/api.php/provide/vod",
      "name": "魔爪资源"
    },
    "mdzy": {
      "api": "https://www.mdzyapi.com/api.php/provide/vod",
      "name": "魔都资源"
    },
    "zuid": {
      "api": "https://api.zuidapi.com/api.php/provide/vod",
      "name": "最大资源"
    },
    "yinghua": {
      "api": "https://m3u8.apiyhzy.com/api.php/provide/vod",
      "name": "樱花资源"
    },
    "wujin": {
      "api": "https://api.wujinapi.me/api.php/provide/vod",
      "name": "无尽资源"
    },
    "wwzy": {
      "api": "https://wwzy.tv/api.php/provide/vod",
      "name": "旺旺短剧"
    },
    "ikun": {
      "api": "https://ikunzyapi.com/api.php/provide/vod",
      "name": "iKun资源"
    }
  }
}
homedia/navidrome/data/...
homedia/openlist/data/...
homedia/portainer/data/...
homedia/qinglong/data/...
homedia/vaultwarden/data/...
homedia/.env
TZ=Asia/Shanghai

4.1 allinone

肥羊的 IPTV 直播源,需要有 token 才能成功部署,获取方式不方便公开讲。但注意,该源纯免费,强烈谴责收费的二道贩子。

4.2 navidrome

音乐私服,直接部署即可,其中一些需要 token 的地方自行获取,但没有亦可部署使用。搭配音流客户端体验更佳。

注意

# 挂载硬盘
mount /dev/sda2 /srv/share
systemctl daemon-reload
# 拷贝文件(带进度显示)
rsync -r --info=progress2 /srv/share/music /mnt/app/
# 查看磁盘占用
du -sh /mnt/app/share/music
# 查看内存占用
free -h

4.3 openlist

作为 AList 的替代品,提供网盘整合服务,也可以挂载本地硬盘,提供多种协议访问,很适合做文件仓库。直接部署即可。

4.4 libretv

提供 VOD 资源,可以看电影、电视剧、综艺等。直接部署即可。

4.5 moontv

同 libretv,只不过这个多了账号体系,但不太成熟,也具有特色。直接部署即可。

4.6 iptvnator

IPTV 网页端播放器,但实测无法播放肥羊源,内置播放器不支持格式,等以后可能更新支持更多播放器。直接部署即可。

4.7 qinglong

支持定义脚本任务定时执行,可以做一些周期性的任务,比如:签到。

4.8 vaultwarden

Bitwarden 的自托管版本,很好用的密码服务器,提供全平台客户端,免费使用。

图标服务器可选:duckduckgobitwardengoogle

注意

vaultwarden 部署成功后,可以方便的将其他平台,如:lastpass、1password、Keepass 等导入进来。但对于 *.kdbx 文件则无法用内置功能迁移,需要借助kp2bw工具。

# windows 下请用 powershell 执行
# 注意启动 kp2bw 后根据提示登录自建的 vaultwarden,但由于一开始我用的自签证书, node 服务器找不到根证书。如果用的可信机构证书应该不会有这问题
# 所以将根证书放进来,设置环境变量再执行登录即可(仅当前会话有效)
$env:NODE_EXTRA_CA_CERTS="./root.crt"
# 登录完成后根据说明将 session 设置为环境变量,例如:
$env:BW_SESSION="9TwD+D2hueP56eC1GI1Z6bl5TisRYBH72Z7Hbs8Nk76uTsPTvoO8ePLIPXNERIAfjZwmv9mUCJPDhcGTs09ThA=="
# 之后继续根据说明操作即可

部署参考:带有 DNS 挑战的 Caddy使用 Let's Encrypt 证书运行私有 Vaultwarden 实例

4.9 jellyfin

一般是 NAS 必备的媒体服务器,可以刮削媒体,比如电影、电视剧、动漫、音乐等,有插件系统提供更多支持。直接部署即可。

4.10 watchtower

监控容器,自动更新容器。直接部署即可。

4.11 portainer

Docker 的管理面板,可以管理容器、镜像、网络、卷、配置、任务、服务、系统信息等等。直接部署即可。

5. honet

文件结构和内容

honet

compose.yaml

homelan.env

caddy.env

sub-store.env

caddy

conf

Caddyfile

config

...

data

...

site

...

haproxy

haproxy.cfg

homelan

...

lucky

data

...

sing-box

cofnig.json

sub-store

data

...

.env

honet/compose.yaml
services:
  # 8000  UDP 广播不会穿透 Docker NAT 网络,即使用端口映射, 那也只影响接收,对容器发出的广播无作用。简单点这里还是用 host 模式
  homelan:
    image: mayeee/homelan:latest
    container_name: homelan
    network_mode: host
    env_file: [homelan.env]
    restart: unless-stopped
    volumes:
      - ./sing-box/config.json:/app/ext/sing-box/config.json:ro
      - /root/.ssh/id_ed25519:/app/ext/ssh/id_ed25519:ro
      - ./homelan:/app/ext
      
  # 55305    
  haproxy:
    image: haproxy:3.2.3
    container_name: haproxy
    network_mode: host
    restart: unless-stopped
    volumes:
      - ./haproxy:/usr/local/etc/haproxy/haproxy.cfg:ro

  sing-box:
    image: ghcr.io/sagernet/sing-box:v1.11.15
    container_name: sing-box
    network_mode: host
    restart: unless-stopped
    volumes:
      - ./sing-box:/etc/sing-box:ro
    command: -D /var/lib/sing-box -C /etc/sing-box/ run

  sub-store:
    image: xream/sub-store:latest
    container_name: sub-store
    ports: [3000:3000, 3001:3001]
    networks: [home]
    env_file: [sub-store.env]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true
    volumes:
      - ./sub-store/data:/opt/app/data

  caddy:
    image: mayeee/caddy:2.10.0
    container_name: caddy
    env_file: [caddy.env]
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./caddy/conf:/etc/caddy
      - ./caddy/site:/srv
      - ./caddy/data:/data
      - ./caddy/config:/config/caddy

  lucky:
    profiles: ['ignore']
    image: gdy666/lucky:2.18.1
    container_name: lucky
    ports: [16601:16601]
    restart: always
    networks: [home]
    volumes:
      - ./lucky/data:/goodluck

networks:
  home:
    external: true
honet/homelan.env
# 需要唤醒的主机 MAC
MAC="xx:xx:xx:xx:xx:xx"
# 需要唤醒的主机 IP
IP="192.168.0.1"
# 唤醒主机的用户
USER="mayee"
# bark 通知,不配置则不通知
DEVICE_KEY="xxxxx"
# aes 密钥, 必须 16 位
AES_KEY="xxxxx"
# aes 向量
AES_IV="xxx"
honet/caddy.env
CLOUDFLARE_API_TOKEN=xxxxxxx
DOMAIN=mayee.dpdns.org
honet/sub-store.env
SUB_STORE_FRONTEND_BACKEND_PATH=/xxxxxxxxxx
honet/caddy/conf/Caddyfile
{
        auto_https disable_redirects
        email maye_e@qq.com
        ocsp_stapling off
}
(cloudflare_tls) {
        tls {
                dns cloudflare {$CLOUDFLARE_API_TOKEN}
                propagation_timeout 300s
        }
}
{$DOMAIN}, *.{$DOMAIN} {
    import cloudflare_tls

    @root host {$DOMAIN}
    reverse_proxy @root http://127.0.0.1:2151

    @code host code.{$DOMAIN}
    reverse_proxy @code http://192.168.0.75:2333

    @git host git.{$DOMAIN}
    reverse_proxy @git http://127.0.0.1:9300

    @db host db.{$DOMAIN}
    reverse_proxy @db http://127.0.0.1:3300

    @vw host vw.{$DOMAIN}
    reverse_proxy @vw http://127.0.0.1:8137
}
honet/caddy/config/...
honet/caddy/data/...
honet/caddy/site/...
honet/haproxy/haproxy.cfg
# Example configuration file for HAProxy 2.0, refer to the url below for
# a full documentation and examples for configuration:
# https://cbonte.github.io/haproxy-dconv/2.0/configuration.html


# Global parameters
global

	# Log events to a remote syslog server at given address using the
	# specified facility and verbosity level. Multiple log options 
	# are allowed.
	#log 10.0.0.1 daemon info

	# Specifiy the maximum number of allowed connections.
	maxconn 32000

	# Raise the ulimit for the maximum allowed number of open socket
	# descriptors per process. This is usually at least twice the
	# number of allowed connections (maxconn * 2 + nb_servers + 1) .
	ulimit-n 65535

	# Drop privileges (setuid, setgid), default is "root" on OpenWrt.
	uid 0
	gid 0

	# Perform chroot into the specified directory.
	#chroot /var/run/haproxy/

	# Daemonize on startup
	daemon

	nosplice
	# Enable debugging
	#debug

	# Spawn given number of processes and distribute load among them,
	# used for multi-core environments or to circumvent per-process
	# limits like number of open file descriptors. Default is 1.
	#nbproc 2

# Default parameters
defaults
	# Default timeouts
	timeout connect 5000ms
	timeout client 50000ms
	timeout server 50000ms

# 自定义 sni 分流
frontend shadowsocks_sni_in
    bind 0.0.0.0:55305
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req.ssl_hello_type 1 }

    # shadow-tls
    acl sni_is_st_1 req.ssl_sni -i toutiao.com
    acl sni_is_st_2 req.ssl_sni -i coding.net
	  acl sni_is_st_3 req.ssl_sni -i mp.weixin.qq.com
	  acl sni_is_st_4 req.ssl_sni -i cdn.bootcdn.net
	  acl sni_is_st_5 req.ssl_sni -i feishu.cn

    # shadow-tls
    use_backend st_ss_1 if sni_is_st_1
    use_backend st_ss_2 if sni_is_st_2
	  use_backend st_ss_3 if sni_is_st_3
	  use_backend st_ss_4 if sni_is_st_4
	  use_backend st_ss_5 if sni_is_st_5

# shadow-tls
backend st_ss_1
    mode tcp
    server shadow_tls_1 127.0.0.1:55315

backend st_ss_2
    mode tcp
    server shadow_tls_2 127.0.0.1:55325

backend st_ss_3
    mode tcp
    server shadow_tls_3 127.0.0.1:55335

backend st_ss_4
    mode tcp
    server shadow_tls_4 127.0.0.1:55345

backend st_ss_5
    mode tcp
    server shadow_tls_5 127.0.0.1:55355

# Special health check listener for integration with external load
# balancers.
listen local_health_check

	# Listen on port 60000
	bind :60000

	# This is a health check
	mode http

	http-request return status 200 content-type "text/plain" string "OK"
	# Enable HTTP-style responses: "HTTP/1.0 200 OK"
	# else just print "OK".
	#option httpchk
honet/homelan/...
honet/lucky/data/...
honet/sing-box/cofnig.json
{
  "dns": {
    "servers": [
      {
        "tag": "tencent",
        "address": "https://doh.pub/dns-query",
        "address_resolver": "dns_resolver",
        "strategy": "prefer_ipv4",
        "detour": "direct"
      },
      {
        "tag": "alibaba",
        "address": "https://dns.alidns.com/dns-query",
        "address_resolver": "dns_resolver",
        "strategy": "prefer_ipv4",
        "detour": "direct"
      },
      {
        "tag": "dns_resolver",
        "address": "223.5.5.5",
        "strategy": "prefer_ipv4",
        "detour": "direct"
      }
    ],
    "rules": [
      {
        "outbound": "any",
        "server": "tencent"
      }
    ]
  },
  "inbounds": [
    {
      "tag": "st-1",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55315,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "toutiao.com",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "st-2",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55325,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "coding.net",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "st-3",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55335,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "mp.weixin.qq.com",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "st-4",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55345,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "cdn.bootcdn.net",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "st-5",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55355,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "feishu.cn",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "ss",
      "type": "shadowsocks",
      "listen": "127.0.0.1",
      "listen_port": 5505,
      "network": "tcp",
      "method": "2022-blake3-aes-128-gcm",
      "password": "xxxx",
      "tcp_fast_open": true,
      "multiplex": {
        "enabled": true,
        "padding": true
      },
      "detour": "direct"
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "type": "direct"
    }
  ]
}
honet/sub-store/data/...
honet/.env
TZ=Asia/Shanghai

5.1 homelan

我写的服务,持续集成自己需要的功能,目前提供的功能有:生成订阅节点用于内网穿透、支撑 WOL 方便手机管理电脑 开机/关机/休眠/唤醒。直接部署即可。

注意

挂载进去的 ssh key 必须只读,确保当前用户有目录权限chown -R mayee:mayee data,且限制文件权限chmod 600 ./id_ed25519,SSH 客户端要求很严格,权限过于宽松的 key 不会使用。

5.2 haproxy

haproxy 是一个负载均衡软件,用于将流量转发到多个后端服务器,这里是用来根据 SNI 分流到 sing-box 的不同端口。直接部署即可。

5.3 sing-box

sing-box 是一个代理软件,用于将流量转发到后端服务器,这里是提供了 5 个 Sahdow-TLS 入口 和 1 个 Shadowsocks 服务端,用作内网穿透服务器。直接部署即可。

5.4 sub-store

sub-store 是一个订阅转换软件,也可以托管订阅节点或者配置文件,还可以对配置进行复写,并输出多种订阅格式。直接部署即可。

5.5 caddy

caddy 是一个反向代理软件,对于一些需要 https 访问的服务,在这里配置反代。直接部署即可。

由于我的内网服务并不想暴露到公网上,在内网时直接用,在外时通过内网穿透用,安全可靠。
使用域名访问就会存在一个问题,如果用自签域名,则每个访问设备都要导入根证书,不安全且麻烦。那就要用可信机构的证书,这个前提必须得有个公网域名,可是问题又来了,申请证书时,我纯内网的服务器如何验证呢?
通常证书发行机构主动验证用的是 HTTP 挑战,而对于纯内网服务器,可以用 DNS 挑战,原理就是把 token 和域名绑定的 txt 内容写入到 DNS 记录,发行机构在一定时间内去验证,如果验证成功就证明你是这个服务器的拥有者,可以给你发证书。

最简单的方式是下载Caddy时勾选上 DNS 模块,但这里我用的是容器部署,所以需要自行编译,好在官方早就提供了xcaddy方便编译。

Dockerfile
FROM caddy:2.10.0-builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare \
    --with github.com/caddy-dns/duckdns

FROM caddy:2.10.0

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

然后运行docker build -t mayeee/caddy:2.10.0 --push .编译并推送到Docker Hub

当发起证书申请时,在 Caddy 的后台可能看到如下日志:

stderr: {"level":"info","ts":1753150377.8356597,"msg":"trying to solve challenge","identifier":"mv.mayee.dpdns.org","challenge_type":"dns-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}

看到如下日志说明证书申请成功了:

stderr: {"level":"info","ts":1753150394.7948797,"msg":"authorization finalized","identifier":"mv.mayee.dpdns.org","authz_status":"valid"}
stderr: {"level":"info","ts":1753150394.794989,"msg":"validations succeeded; finalizing order","order":"https://acme-v02.api.letsencrypt.org/acme/order/2540810831/409490037471"}
stderr: {"level":"info","ts":1753150395.8999088,"msg":"got renewal info","names":["mv.mayee.dpdns.org"],"window_start":1758254328,"window_end":1758409778,"selected_time":1758359327,"recheck_after":1753171995.8998837,"explanation_url":""}
stderr: {"level":"info","ts":1753150396.3523867,"msg":"got renewal info","names":["mv.mayee.dpdns.org"],"window_start":1758254328,"window_end":1758409778,"selected_time":1758352057,"recheck_after":1753171996.3523502,"explanation_url":""}
stderr: {"level":"info","ts":1753150396.352736,"msg":"successfully downloaded available certificate chains","count":2,"first_url":"https://acme-v02.api.letsencrypt.org/acme/cert/06fc056c35ccb80c775af08e52af41b0c704"}
stderr: {"level":"info","ts":1753150396.3627076,"logger":"tls.obtain","msg":"certificate obtained successfully","identifier":"mv.mayee.dpdns.org","issuer":"acme-v02.api.letsencrypt.org-directory"}
stderr: {"level":"info","ts":1753150396.3632228,"logger":"tls.obtain","msg":"releasing lock","identifier":"mv.mayee.dpdns.org"}
stderr: {"level":"warn","ts":1753150396.3652065,"logger":"tls","msg":"stapling OCSP","error":"no OCSP stapling for [mv.mayee.dpdns.org]: no OCSP server specified in certificate","identifiers":["mv.mayee.dpdns.org"]}

至于提示no OCSP stapling不用管,这只是个警告,不影响证书使用和续签。OCSP 就是让客户端可以实时验证证书状态,是否 still valid 或 revoked,Let's Encrypt 的证书通常不会包含 OCSP URL。申请 Let's Encrypt 证书时必须要有邮箱,并且限制一个邮箱 7 天内最多申请 50 个证书。

5.6 lucky

lucky 是一个家用软硬路由公神器。它集成了很多功能,但对我来说最有用的功能就是提供 STUN 内网穿透,只要是 NAT1 类型的网络都可以穿透。穿透之后就好处多多了,在外也可以访问到内网。

6. 常用命令

说明命令备注
创建 Stackdocker compose up -d会自动创建网络和数据卷(如果有定义),若镜像不存在则自动拉镜像,否则直接用现有镜像创建
销毁 Stackdocker compose [-p <stack_name>] down会停止并删除定义的容器和网络,但不会删除数据卷。
重建容器(不拉新镜像)docker compose up -d --no-deps --force-recreate <service_name>用于 docker-compose 文件配置更改了,重建容器
重建容器(拉新镜像)docker compose up -d --no-deps --force-recreate --pull always <service_name>用于镜像更新了,重建容器
创建数据卷docker compose create volume
查看卷docker volume inspect <volume_name>
删除数据卷docker compose rm volume
创建网络docker compose create network
查看网络docker volume inspect <network_name>
删除网络docker compose rm network
删除所有无标签镜像docker image prune -f-a 表示删除所有未被任何容器使用的镜像(包括有 tag 的老版本)

如果要删除多个卷:docker volume ls -q | xargs docker volume rm,创建多个卷:echo "mysql-conf mysql-data redis" | xargs -n 1 docker volume create,其他命令同理。