跳转至

主节点部署 (Security Hardened)

[!CAUTION] 前置条件:请务必先完成 安全基线与加固 的检查,确保防火墙和系统配置已加固。

部署流程

0. OS: 安全初始化 (防火墙 + SSH加固) -> 见《安全基线与加固》
      ↓
1. CLI: 部署基础组件 (Tailscale + Portainer)
      ↓
2. 隧道: 使用 SSH Tunnel 首次连接 Portainer (Localhost Only)
      ↓
3. 验证: 确认 Tailscale0 网卡状态
      ↓
4. 网关: 部署 Traefik (严禁开启 insecure dashboard)
      ↓
5. 应用: 部署 Homepage / Beszel

1. 基础组件部署 (CLI)

推荐目录:/opt/infra/bootstrap

1.1 准备环境

sudo mkdir -p /opt/infra/bootstrap
sudo chown -R $USER:$USER /opt/infra
ln -s /opt/infra ~/infra

# 1. 检查并加载 TUN 模块
if [ ! -c /dev/net/tun ]; then
  sudo modprobe tun
fi

cd ~/infra/bootstrap

1.2 配置文件 (docker-compose.yml)

[!SECURE] 安全变更: 1. Portainer 仅监听 127.0.0.1:9000,杜绝公网扫描。 2. Tailscale 移除 privileged: true,改用 CapAdd (最小权限)。

services:
  # 1. 网络底座 (Host Mode)
  net-tailscale:
    image: tailscale/tailscale:latest
    container_name: net-tailscale
    hostname: chuncheon1
    network_mode: host
    restart: always
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    volumes:
      - ./tailscale-data:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    environment:
      - TS_AUTHKEY=tskey-auth-kBZ4MoVYE321CNTRL-B4VqdYsdmMRh5sp5bn7wMRz3rThQTwXT
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=false
      - TS_EXTRA_ARGS=--accept-routes

  # 2. 核心管理 (Bridge Mode + Traefik Secure Access)
  ops-portainer:
    image: 6053537/portainer-ce:latest
    container_name: ops-portainer
    restart: always
    ports:
      # [Break-Glass] 仅保留 127.0.0.1 以备 Traefik 挂掉时通过 SSH Tunnel 救急
      - "127.0.0.1:9000:9000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./portainer-data:/data
    networks:
      - proxy-kr
    labels:
      - "traefik.enable=true"
      # [Router] 域名访问 (建议使用 *.i.120224.xyz 并解析到 Tailscale IP)
      - "traefik.http.routers.portainer.rule=Host(`portainer.i.120224.xyz`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
      - "traefik.http.routers.portainer.service=portainer"
      - "traefik.http.services.portainer.loadbalancer.server.port=9000"
      # [Middleware] 强制应用 IP 白名单 (Tailscale Only)
      - "traefik.http.routers.portainer.middlewares=security-ip-whitelist@docker"

networks:
  proxy-kr:
    name: proxy-kr
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24

1.3 启动

echo "TS_AUTHKEY=tskey-auth-xxxxx" > .env
docker compose up -d force-recreate

2. 首次访问 (SSH Tunnel)

由于 Portainer 只监听了 127.0.0.1,你必须打洞才能访问。

2.1 建立隧道 (在你的本地电脑执行)

# 格式: ssh -L 本地端口:127.0.0.1:远程端口 用户@服务器IP
ssh -L 9000:127.0.0.1:9000 user@chuncheon1.yourdomain.com

2.2 初始化与救急 (Break-Glass)

场景:Traefik 挂了/配置错了,导致 https://portainer.i... 无法访问。此时必须通过 SSH 隧道绕过 Traefik,直接连接 Portainer 的 127.0.0.1:9000 端口。

  1. 建立隧道 (在本地电脑执行):

    # 将服务器的 localhost:9000 映射到你电脑的 9000
    ssh -L 9000:127.0.0.1:9000 user@chuncheon1.yourdomain.com
    
    (保持 SSH 窗口开启不要关闭)

  2. 访问面板:

    • 浏览器打开: http://localhost:9000
    • 现在你直接连到了 Portainer 内部,不受 Traefik 影响。
    • 在这里你可以重启 Traefik 容器,或修复配置。
  3. 常规初始化:

    • 首次登录设置管理员密码。
    • 完成后,Portainer 就运行在安全环境中,外网扫描不到,平时通过 Traefik 域名访问。

3. 部署 Traefik-KR (区域网关)

[!SECURE] Dashboard 安全:严禁使用 --api.insecure=true

  1. 准备目录:

    sudo mkdir -p /opt/infra/stacks/net-traefik-kr/dynamic
    sudo touch /opt/infra/stacks/net-traefik-kr/acme.json
    sudo chmod 600 /opt/infra/stacks/net-traefik-kr/acme.json
    

  2. 部署 Stack (Portainer):

  3. Stack Name: net-traefik-kr
services:
  net-traefik-kr:
    image: traefik:v3.6.7
    container_name: net-traefik-kr
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    environment:
      # ⚠️ 必填:DNSPod 认证信息 (ID,Token)
      - DNSPOD_API_KEY=YOUR_ID,YOUR_TOKEN
      - DNSPOD_HTTP_TIMEOUT=30
    command:
      - "--log.level=INFO"
      - "--api.dashboard=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.websecure.address=:443"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=proxy-kr"

      # [DNS-01 验证] (无惧防火墙/端口限制)
      - "--certificatesresolvers.letsencrypt.acme.email=your@email.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=dnspod"
      - "--certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=120"
      - "--certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,119.29.29.29:53"

    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /opt/infra/stacks/net-traefik-kr/acme.json:/acme.json
    networks:
      - proxy-kr
    labels:
      - "traefik.enable=true"
      # [Dashboard] 路由配置
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.i.120224.xyz`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.tls=true" # 显式开启 TLS
      - "traefik.http.routers.dashboard.tls.domains[0].main=*.i.120224.xyz"
      - "traefik.http.routers.dashboard.tls.domains[0].sans=i.120224.xyz"

      - "traefik.http.routers.dashboard.middlewares=auth,security-ip-whitelist@docker"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"

      # [Security] 定义通用的 IP 白名单 (Localhost + Tailscale Subnet + Docker Gateway)
      # 1. 127.0.0.1/32: 本机访问及 SSH Tunnel
      # 2. 100.64.0.0/10: Tailscale 网络
      # 3. 172.20.0.1/32: Docker 网关 (固定 IP) - 仅信任网关转发的流量,拒绝容器间横向攻击
      - "traefik.http.middlewares.security-ip-whitelist.ipwhitelist.sourcerange=127.0.0.1/32,100.64.0.0/10,172.20.0.1/32"

      # [Auth] Dashboard 基础认证 (htpasswd 生成)
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$..."

networks:
  proxy-kr:
    external: true

3.1 开放防火墙 (必做)

为了防止 Docker 绕过防火墙,我们在《安全基线》中安装了 ufw-docker。这会导致默认拦截 Traefik 的 80/443。 必须显式允许 Traefik 容器对外提供服务:

# 允许外部访问 Traefik 的 HTTP/HTTPS
sudo ufw-docker allow net-traefik-kr 80/tcp
sudo ufw-docker allow net-traefik-kr 443/tcp

# 验证 (应该看到 80/443 ALLOW FWD)
sudo ufw status | grep 80

4. 部署管理应用 (Homepage / Beszel)

配置同原文档,但请确保: 1. 不要映射端口 (如 ports: - 3000:3000 要删除),全靠 Traefik 反向代理。 2. 如果必须映射,请使用 127.0.0.1:3000:3000