开源图片服务器架构

compose   基本操作

docker compose down        # 停止并删除旧容器
docker compose up -d       # 重新以新配置启动

| 命令                               | 作用                   |
| -------------------------------- | -------------------- |
| `docker compose ps`              | 查看当前 Compose 项目的容器状态 |
| `docker compose logs -f`         | 实时查看日志               |
| `docker compose exec nginx bash` | 进入指定容器               |
| `docker compose down`            | 停止并清理所有容器、网络、卷       |
| `docker compose restart`         | 快速重启所有容器             |
compose    进入容器

# 用服务名(compose 推荐)
docker compose exec piwigo bash
# 如果没有 bash 就用 sh
docker compose exec piwigo sh

# 或者用容器名
docker exec -it pwg_app bash


------------------------FileBrowser---------------

一、目录结构
/opt/data/
  ├─ user1_dir/
  ├─ user2_dir/
  ├─ user3_dir/
  ├─ user4_dir/
  └─ (可按类目继续建子目录,如 20230614/ 备份/ WK-001-P1/ ...)
~/imgstack/
  ├─ docker-compose.yml
  ├─ nginx/
  │   └─ conf.d/
  │       ├─ admin.amztk.top.conf
  │       └─ img.amztk.top.conf
  ├─ certs/
  │   ├─ admin/   # admin.amztk.top 证书
  │   │   ├─ fullchain.pem
  │   │   └─ privkey.pem
  │   └─ img/     # img.amztk.top / img.amztk.com 证书
  │       ├─ fullchain.pem
  │       └─ privkey.pem
  └─ filebrowser/
      ├─ settings.json
      └─ filebrowser.db   # 自动生成


先创建目录:

sudo mkdir -p /opt/data/{user1_dir,user2_dir,user3_dir,user4_dir}
sudo mkdir -p ~/imgstack/nginx/conf.d ~/imgstack/certs/{admin,img}
cd ~/imgstack/filebrowser
sudo chown -R 1000:1000 ./filebrowser
sudo chown -R 1000:1000 /opt/data

在 ~/imgstack/docker-compose.yml  写入

services:
  nginx:
    image: nginx:1.25-alpine
    container_name: img-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./certs:/etc/nginx/certs:ro
      - /opt/data:/data:ro
    depends_on:
      - filebrowser
    networks: [imgnet]

  filebrowser:
    image: filebrowser/filebrowser:latest
    container_name: img-filebrowser
    restart: unless-stopped
    # 如需严格权限可改为特定 uid:gid
    user: "1000:1000"
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - /opt/data:/srv
      - ./filebrowser:/config
      
    command: >
      --address=0.0.0.0
      --port=8080
      --root=/srv
      --database=/config/filebrowser.db
      --config=/config/settings.json
    expose:
      - "8080"
    networks: [imgnet]

networks:
  imgnet:
    driver: bridge


FileBrowser 基础配置  ~/imgstack/filebrowser/settings.json:

{
  "branding": {
    "name": "AMZ 图库",
    "disableExternal": true
  },
  "signup": false,
  "createUserDir": false,
  "commands": []
}



Nginx 配置(SSL + 反代后台 + 静态分发 + 防盗链)

后台:admin.amztk.top.conf  ~/imgstack/nginx/conf.d/admin.amztk.top.conf

# 强制跳转 HTTPS
server {
  listen 80;
  server_name admin.amztk.top;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  server_name admin.amztk.top;

  ssl_certificate     /etc/nginx/certs/admin/fullchain.pem;
  ssl_certificate_key /etc/nginx/certs/admin/privkey.pem;

  client_max_body_size 200m;

  location / {
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://img-filebrowser:8080;
  }
}


前台:img.amztk.top.conf  ~/imgstack/nginx/conf.d/img.amztk.top.conf

# 兼容 .top/.com,统一跳 HTTPS
server {
  listen 80;
  server_name img.amztk.top img.amztk.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  server_name img.amztk.top img.amztk.com;

  ssl_certificate     /etc/nginx/certs/img/fullchain.pem;
  ssl_certificate_key /etc/nginx/certs/img/privkey.pem;

  # 静态根目录(只读)
  root /data;
  autoindex off;

  # 防盗链:只允许来自本站域名与无 Referer(直链)
  # 若你还有其他站点需要引用,在下方 server_names 后面追加允许的域名
  set $deny_hotlink 0;
  valid_referers none blocked server_names *.amztk.top *.amztk.com;
  if ($invalid_referer) { set $deny_hotlink 1; }

  # 仅对常见图片后缀启用缓存与防盗链
  location ~* \.(?:png|jpe?g|gif|webp|svg|avif|bmp)$ {
    if ($deny_hotlink) { return 403; }
    expires 30d;
    add_header Cache-Control "public, max-age=2592000, immutable";
    try_files $uri =404;
  }

  # 其他静态资源(可选)
  location / {
    if ($deny_hotlink) { return 403; }
    try_files $uri $uri/ =404;
  }
}


如果你希望完全禁止无 Referer 的直链,把 valid_referers 里去掉 none;

如果要给 Amazon、Shopify 等站点放行引用,追加它们的域名到 valid_referers 列表中。

若想盗链时返回占位图,可把 return 403; 换成 rewrite ^ /placeholder.png last; 并把占位图放到 /opt/data/placeholder.png。


启动服务

cd ~/imgstack

docker compose down        # 停止并删除旧容器
docker compose up -d       # 重新以新配置启动


# 查看容器状态与日志
docker compose ps
docker compose logs -f nginx
docker compose logs -f filebrowser


修改fileBrowser admin密码

# 停止容器
docker compose stop filebrowser

# 执行命令创建用户(此时数据库不再被占用)
docker compose run --rm filebrowser sh -c \
  'filebrowser -d /config/filebrowser.db -c /config/settings.json users add admin "admintpassword" --perm.admin --scope /srv'

# 启动容器
docker compose up -d


修改密码  

# 停止容器(防止 DB 被占用)
docker compose stop filebrowser

# 修改 admin 密码(替换成你的密码)
docker compose run --rm filebrowser \
  -d /config/filebrowser.db -c /config/settings.json \
  users update admin --password "新密码"

# 启动容器
docker compose up -d filebrowser



img.amztk.top  没有权限访问图片

Nginx 的 UID(通常是 101) 授权读权限,并设置 默认 ACL,以后新文件自动继承。

# 假设 Nginx 在容器内的用户是 uid=101(nginx:nginx,Alpine 默认)
# 1) 先给现有文件/目录加读/遍历权限
sudo setfacl -R -m u:101:rX /opt/data

# 2) 给目录设置“默认 ACL”,以后新建的文件也自动给 101 读权限
sudo setfacl -R -m d:u:101:rX /opt/data


如你的 Nginx 不是 uid 101,可进入容器查:

docker compose exec nginx sh -c 'id -u nginx || id -u' 然后把上面的 101 替换成实际 UID





-------------------------Piwigo-------------------
Piwigo + Nginx 部署架构

Cloudflare (CDN + HTTPS)
        ↓
Nginx(反向代理 + 缓存 + 防盗链)
        ↓
Piwigo(图片管理 + 上传 + 分类)
        ↓
/data/photos (存储目录)
/opt/pwg/
  docker-compose.yml
  nginx/
    site.conf
  photos/                 # 你管理的“源图”目录(按 SKU 命名/分层)
  piwigo_data/            # Piwigo 程序与配置
  db_data/                # 数据库持久化

1, 准备目录

mkdir -p /opt/pwg/{nginx,piwigo_data,db_data,photos,tools,nginx/cache}

2, 保存 Compose 与配置文件

/opt/pwg/docker-compose.yml

/opt/pwg/nginx/site.conf

/opt/pwg/tools/sku_links.sh


3, 将以下内容保存为 /opt/pwg/docker-compose.yml


services:
  db:
    image: mariadb:10.11
    container_name: pwg_db
    environment:
      MYSQL_ROOT_PASSWORD: root_pass_123
      MYSQL_DATABASE: piwigo
      MYSQL_USER: piwigo
      MYSQL_PASSWORD: pwg_pass_123
    volumes:
      - ./db:/var/lib/mysql
    restart: unless-stopped

  piwigo:
    image: lscr.io/linuxserver/piwigo:latest
    container_name: pwg_app
    depends_on:
      - db
    environment:
      PUID: 1000
      PGID: 1000
      TZ: Asia/Shanghai
    volumes:
      - ./piwigo_data:/config                           # 配置/缓存/日志
      - ./plugins:/config/www/plugins                   # 可选:外挂插件目录
      - ./photos:/config/www/_data/i/upload             # ★ 后台上传“原图”目录(与 FTP/SFTP/Nginx 共用)
    restart: unless-stopped

  nginx:
    image: nginx:1.25-alpine
    container_name: pwg_nginx
    depends_on:
      - piwigo
    volumes:
      - ./nginx/site.conf:/etc/nginx/conf.d/default.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - ./nginx/cache:/var/cache/nginx
      - ./photos:/photos:ro                             # ★ 前台直链读同一目录
      - ./piwigo_data:/config:ro
    ports:
      - "80:80"
      - "443:443"
    restart: unless-stopped

  ftp:
    image: fauria/vsftpd
    container_name: pwg_ftp
    environment:
      - FTP_USER=amz
      - FTP_PASS=yourpassword123
      - PASV_ADDRESS=img.amztk.top
      - PASV_MIN_PORT=21100
      - PASV_MAX_PORT=21110
      - LOG_STDOUT=1
      # 可选:写入权限更宽松一点
      - FILE_OPEN_MODE=0666
      - LOCAL_UMASK=022
    volumes:
      - ./photos:/home/vsftpd                           # ★ 与 Piwigo 同目录
    ports:
      - "21:21"
      - "21100-21110:21100-21110"
    restart: unless-stopped

  sftp:
    image: atmoz/sftp
    container_name: pwg_sftp
    volumes:
      - ./photos:/home/amz/upload                       # ★ 与 Piwigo 同目录
    # 语法:user:pass:uid:gid  (uid/gid 用 1000,与 Piwigo 一致)
    command: amz:yourpassword123:1000:1000
    ports:
      - "2222:22"
    restart: unless-stopped
nginx配置 保存为 /opt/pwg/nginx/site.conf

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=imgcache:200m inactive=7d max_size=20g;

# 80 -> 443
server {
  listen 80;
  server_name img.amztk.top;
  return 301 https://img.amztk.top$request_uri;
}
server {
  listen 80;
  server_name admin.amztk.top;
  return 301 https://admin.amztk.top$request_uri;
}

# ===============================================
# img.amztk.top — 图片直链(根路径 & 兼容 /photos/)
# ===============================================
server {
  listen 443 ssl;
  server_name img.amztk.top;

  ssl_certificate     /etc/nginx/ssl/img.crt;
  ssl_certificate_key /etc/nginx/ssl/img.key;

  # ① 兼容旧链接:/photos/... -> /photos/...
  location ^~ /photos/ {
    root /;                       # /photos/... 直接映射到容器 /photos/...
    try_files $uri =404;

    # 防盗链(保持你的逻辑)
    valid_referers none blocked amztk.top *.amztk.top amazon.com *.amazon.com sellercentral.amazon.com m.media-amazon.com images-na.ssl-images-amazon.com;
    # if ($invalid_referer) { return 403; }

    add_header Cache-Control "public, max-age=31536000, immutable";
    expires 1y;
    etag on;

    proxy_cache imgcache;
    proxy_cache_valid 200 206 301 302 10d;
  }

  # ② 新增:根路径直接访问 -> 映射到 /photos
  #    https://img.amztk.top/2025/10/25/a.jpg => /photos/2025/10/25/a.jpg
  location / {
    root /photos;                 # 关键:把站点根映射到 /photos
    try_files $uri =404;

    # 同样的防盗链和缓存策略
    valid_referers none blocked amztk.top *.amztk.top amazon.com *.amazon.com sellercentral.amazon.com m.media-amazon.com images-na.ssl-images-amazon.com;
    # if ($invalid_referer) { return 403; }

    add_header Cache-Control "public, max-age=31536000, immutable";
    expires 1y;
    etag on;

    proxy_cache imgcache;
    proxy_cache_valid 200 206 301 302 10d;
  }

  # 禁止脚本(覆盖根路径与 /photos/)
  location ~* \.(php|sh|pl|py)$ {
    return 403;
  }

  add_header Access-Control-Allow-Origin "*" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header Referrer-Policy "no-referrer-when-downgrade" always;
}

# ===============================================
# admin.amztk.top — 后台(反代到 pwg_app:80)
# ===============================================
server {
  listen 443 ssl;
  server_name admin.amztk.top;

  ssl_certificate     /etc/nginx/ssl/admin.crt;
  ssl_certificate_key /etc/nginx/ssl/admin.key;

  location / {
    proxy_pass http://pwg_app:80;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
  }

  add_header X-Content-Type-Options "nosniff" always;
}

4, 准备证书

Cloudflare Origin Certificate(推荐配合 Cloudflare 的 Full (strict)):
在 Cloudflare → SSL/TLS → Origin Server → Create…
新建证书,域名填:img.example.com、admin.example.com(可以一个证书含多个主机名)。
把生成的 证书 与 私钥 分别保存为(示例):

/opt/pwg/nginx/ssl/img.crt
/opt/pwg/nginx/ssl/img.key
/opt/pwg/nginx/ssl/admin.crt
/opt/pwg/nginx/ssl/admin.key

5, 启动docker
docker compose down      
docker compose up -d

发表回复