在VPS上使用Docker Compose自建Immich

在VPS上部署Immich,作为自建的Google Photos替代方案。涵盖Docker Compose配置、手机自动备份、真实数据的存储规划、CPU上的机器学习、数据库备份与恢复,以及安全的更新流程。

Immich是一个开源的自建照片和视频管理平台。它提供手机自动备份、人脸识别、智能搜索和相册共享功能。如果你想停止将照片发送给Google,Immich是最接近的自建替代方案。

在欧洲VPS上运行Immich意味着你的照片留在你控制的基础设施上。没有第三方扫描,没有训练数据集,没有突然的政策变更。从GDPR角度看,你同时是数据控制者和处理者,当数据永远不离开你的服务器时,合规变得更简单。

本指南使用Docker Compose在远程VPS上部署Immich。假设你已经安装了带有Compose插件的Docker Engine,并且有一个反向代理在运行。如果需要先安装这些,请参阅VPS上的Docker生产环境:会出什么问题以及如何解决Traefik vs Caddy vs Nginx:Docker反向代理对比

运行Immich需要什么配置?

Immich在启用机器学习的完整部署中至少需要6 GB内存和2个CPU核心。禁用ML时,4 GB即可。存储取决于你的照片库:按照原始库大小加上10-20%的缩略图和转码视频开销,再加上1-3 GB的PostgreSQL数据库来规划。需要带Compose插件的Docker Engine。Immich仅在Linux上运行。

组件 最低配置 推荐配置
内存 4 GB(禁用ML) 8 GB
CPU 2核 4核
存储 50 GB(小型库) 250 GB+
软件 Docker Engine + Compose插件 同上
操作系统 任何支持Docker的Linux Ubuntu 22.04/24.04、Debian 12

PostgreSQL需要本地SSD存储。永远不要将数据库放在网络共享上。在Virtua VPS上,NVMe是默认配置,所以这已经满足了。

如何使用Docker Compose部署Immich?

从Immich发布页面下载官方Docker Compose文件和示例环境文件,配置存储路径和数据库密码,然后启动堆栈。

创建项目目录

mkdir -p /opt/immich && cd /opt/immich

下载官方文件

始终从最新版本下载这些文件。不要从博客文章(包括这篇)中复制粘贴,因为Immich会频繁更新其Compose文件以匹配数据库镜像的变化。

wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env

配置环境

打开.env文件:

nano .env

设置以下值:

UPLOAD_LOCATION=/opt/immich/data
DB_DATA_LOCATION=/opt/immich/postgres
IMMICH_VERSION=v2.6.1
DB_PASSWORD=<你的强密码>
TZ=Europe/Berlin

使用纯字母数字字符(A-Za-z0-9)生成强数据库密码。特殊字符会导致.env文件中的Docker Compose解析问题:

openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32; echo

复制输出并粘贴为DB_PASSWORD的值。

IMMICH_VERSION固定到特定的发布标签如v2.6.1,而不是使用v2元标签。这可以防止在你为其他服务运行docker compose pull时发生意外升级。你来控制Immich何时更新。

创建数据目录

mkdir -p /opt/immich/data /opt/immich/postgres

移除暴露的端口

默认的Compose文件直接暴露端口2283。由于你使用反向代理,请在docker-compose.yml中删除或注释掉ports部分,使Immich只能通过代理访问:

sed -i "s/- '2283:2283'/# - '2283:2283'/" docker-compose.yml

将Immich连接到反向代理的Docker网络。如果代理网络名为proxy

cat >> docker-compose.yml << 'EOF'

networks:
  default:
  proxy:
    external: true
EOF

然后将proxy网络添加到immich-server服务。编辑docker-compose.yml,在immich-server服务下添加:

    networks:
      - default
      - proxy

这样数据库和Redis保持在内部网络,而只有服务器暴露给反向代理。

启动堆栈

docker compose up -d

查看日志确认所有服务正常启动:

docker compose logs -f --tail=50

服务器日志在准备就绪后显示Immich Server is listening on http://[::1]:2283。机器学习服务随后加载模型。按Ctrl+C退出日志流。

检查四个容器是否都在运行:

docker compose ps
NAME                    STATUS
immich_server           Up (healthy)
immich_machine_learning Up (healthy)
immich_redis            Up (healthy)
immich_postgres         Up (healthy)

四个容器都应该在几分钟内显示Up (healthy)。ML容器耗时最长,因为它在首次启动时下载模型。

限制文件权限

.env文件包含你的数据库密码。锁定它:

chmod 600 .env
ls -la .env
-rw------- 1 root root 245 Mar 20 10:00 .env

如何为Immich配置反向代理?

Immich需要反向代理来提供HTTPS并处理来自移动设备的大文件上传。你必须将请求体大小限制设置得足够高以支持视频上传。否则,超过默认限制(Nginx通常为1 MB)的上传会静默失败。

完整的反向代理配置请参阅Traefik vs Caddy vs Nginx:Docker反向代理对比。以下是Immich专用的配置片段。

Caddy

在你的Caddyfile中:

photos.example.com {
    reverse_proxy immich_server:2283
}

Caddy自动处理TLS,并且没有默认的请求体大小限制,因此视频上传开箱即用。

Traefik

docker-compose.yml中为immich-server服务添加标签:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.immich.rule=Host(`photos.example.com`)"
      - "traefik.http.routers.immich.entrypoints=websecure"
      - "traefik.http.routers.immich.tls.certresolver=letsencrypt"
      - "traefik.http.services.immich.loadbalancer.server.port=2283"

对于Traefik的大文件上传,添加中间件增加缓冲限制,或在Traefik静态配置中设置maxRequestBodyBytes

Nginx

如果使用Nginx作为反向代理,关键设置是client_max_body_size。没有它,视频上传会失败:

server {
    server_name photos.example.com;

    client_max_body_size 50000M;

    location / {
        proxy_pass http://immich_server:2283;
        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;

        # WebSocket support for real-time updates
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

将DNS A记录(如果有IPv6则还有AAAA记录)指向你的VPS IP地址。DNS传播后,通过https://photos.example.com访问Immich。

首次登录后如何保护Immich?

在浏览器中打开https://photos.example.com。Immich显示注册页面。使用强密码创建管理员账户。

创建管理员账户后,立即禁用公开注册。进入Administration > Settings > Server并关闭Allow New Users。否则任何发现你URL的人都可以创建账户并上传文件到你的存储空间。

需要检查的管理面板设置

  • Administration > Settings > Storage Template:启用它以按日期组织文件({{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}})而不是随机UUID。这使手动浏览和选择性恢复成为可能。
  • Administration > Settings > Backup:自动数据库备份默认开启,每天凌晨2:00运行,保留14天。确认这是活跃的。
  • Administration > Settings > Server:将外部URL设置为你的域名(https://photos.example.com)。移动应用和共享链接会使用它。

容器级别的安全加固

docker-compose.yml中为immich-server服务添加安全选项:

    security_opt:
      - no-new-privileges:true

这防止容器内的权限提升。no-new-privileges标志阻止setuid二进制文件获取提升的权限。

对于资源限制,添加内存约束以防止单个容器消耗所有VPS内存并触发OOM killer。完整语法请参阅Docker Compose资源限制、健康检查与重启策略。8 GB VPS的实用起点:

    deploy:
      resources:
        limits:
          memory: 2G

immich-serverimmich-machine-learning设置此限制。PostgreSQL和Redis在8 GB VPS上很少需要显式限制,但在4 GB实例上会受益。

隐藏版本信息

Immich默认在API响应中暴露其版本。虽然这不是直接的漏洞,但版本泄露帮助攻击者定位已知问题。没有内置开关来隐藏它,但你的反向代理可以剥离响应头。在Nginx中:

proxy_hide_header X-Powered-By;

如何连接Immich手机应用进行自动备份?

从App Store(iOS)或Google Play(Android)安装Immich应用。首次启动时,输入服务器URL(https://photos.example.com)并使用管理员凭据登录。应用会提示你启用自动备份。

配置自动备份

  1. 打开应用,点击右上角的头像,然后点击Backup Settings
  2. 启用Background Backup。在iOS上,还要在系统设置中启用Background App Refresh。在Android上,为Immich禁用电池优化,防止系统杀死它。
  3. 选择要备份的相册。默认情况下,Immich备份你的相机胶卷。你可以从相册选择界面添加其他相册(截图、WhatsApp图片)。
  4. 仅当你的手机套餐允许时才启用Cellular Backup。大型视频上传可能消耗数GB流量。

首次备份测试

拍一张照片,打开Immich应用,下拉刷新。在Wi-Fi下照片应在几秒内出现在时间线中。应用中的备份状态指示器在所有照片同步后显示绿色勾号。

从VPS上可以确认上传已到达:

ls /opt/immich/data/upload/

你会看到一个以你的用户ID命名的目录,其中包含上传的文件。

Immich每张照片需要多少存储空间?

VPS上的存储规划比NAS更重要,因为你不能简单地插入另一块硬盘。以下是基于典型智能手机照片库的真实数据。

每种照片类型的存储空间

格式 平均大小 备注
智能手机JPEG 3-5 MB 最常见格式
HEIC(iPhone) 2-3 MB Apple从iPhone 7起的默认格式
RAW(单反) 25-50 MB 专业相机
智能手机视频(1080p) ~150 MB/分钟 因编码器而异
智能手机视频(4K) ~400 MB/分钟 H.265节省约40%

按库大小的总存储量

此表使用智能手机JPEG平均4 MB,加上Immich开销(缩略图、转码、数据库)。

库大小 原始照片 缩略图(约15%) 数据库 总计
10,000张照片 40 GB 6 GB 1 GB ~47 GB
25,000张照片 100 GB 15 GB 1.5 GB ~117 GB
50,000张照片 200 GB 30 GB 2 GB ~232 GB
100,000张照片 400 GB 60 GB 3 GB ~463 GB
200,000张照片 800 GB 120 GB 4 GB ~924 GB

如果你的库包含视频,需要相应增加存储需求。一个包含10%视频内容(按文件数计算)的库可能使存储需求翻倍,因为视频每个文件比照片大30-100倍。

何时添加更多存储

在Virtua VPS上,你可以在主磁盘满时附加额外的块存储卷。挂载卷并将UPLOAD_LOCATION指向它。对于超过500 GB的库,考虑使用S3兼容的外部存储。Immich通过其存储配置支持这一功能,将媒体卸载到对象存储,同时保持数据库和缩略图在本地。

使用简单的cron检查监控磁盘使用情况:

df -h /opt/immich/data

当使用率超过80%时设置警报。磁盘满会损坏PostgreSQL数据库,可能使你的Immich实例在没有备份的情况下无法恢复。

v2.5的「Free Up Space」功能

Immich v2.5+在移动应用中包含一个「Free Up Space」按钮。照片备份到服务器后,应用可以删除手机上的本地副本以释放空间。它只删除已确认上传且不在Immich回收站中的文件。iOS和Android都支持此功能。

Immich的机器学习在没有GPU的情况下如何工作?

Immich的ML功能(人脸识别、通过CLIP的智能搜索和物体检测)默认在CPU上运行。不需要GPU。immich-machine-learning容器将模型加载到内存中,在后台处理照片,不会阻塞上传或浏览。

在4核8 GB内存的VPS上,初始扫描的大致处理时间如下:

库大小 人脸检测 CLIP索引 总计(顺序)
1,000张照片 ~15分钟 ~20分钟 ~35分钟
10,000张照片 ~2.5小时 ~3.5小时 ~6小时
50,000张照片 ~12小时 ~17小时 ~29小时

这些是一次性的成本。初始扫描后,新上传在几秒内处理完成。ML容器以低优先级运行,在处理队列时不会阻塞照片上传或浏览。

模型选择

Immich使用两个主要ML模型:

  • 人脸识别:检测并聚类你库中的人脸。每次上传时自动运行。
  • CLIP(智能搜索):按内容索引照片,让你可以搜索「日落」或「海滩上的狗」而无需标签。比人脸识别使用更多内存。

两个模型在首次需要时加载到内存中,5分钟不活动后卸载(默认MACHINE_LEARNING_MODEL_TTL=300)。在内存有限的VPS上,你可以降低此值以更快释放内存:

# In .env
MACHINE_LEARNING_MODEL_TTL=60

修改环境变量后重新创建容器(重启是不够的):

docker compose up -d --force-recreate immich-machine-learning

各组件的内存分配

组件 内存使用(8 GB VPS) 内存使用(4 GB VPS)
immich-server ~500 MB ~500 MB
immich-machine-learning ~1.5-2 GB 禁用
PostgreSQL ~500 MB-1 GB ~500 MB
Redis (Valkey) ~50 MB ~50 MB
操作系统 + Docker开销 ~1 GB ~1 GB
可用余量 ~3-4 GB ~2 GB

如何在小型VPS上禁用ML以节省资源?

如果你使用4 GB VPS或想节省资源,从docker-compose.yml中删除或注释掉immich-machine-learning服务:

cd /opt/immich

编辑docker-compose.yml并注释掉(或删除)整个immich-machine-learning服务块。然后重启:

docker compose up -d

你会失去人脸识别、智能搜索和物体检测。照片上传、浏览、共享和相册管理正常工作。你可以稍后通过取消注释服务并重启来重新启用ML。

如何备份Immich的数据库和照片?

备份两样东西:用pg_dump备份PostgreSQL数据库,用rsync备份上传目录。两者都用cron定时执行。不要在备份目标上使用rsync --delete,因为源端的损坏会传播到你的备份。至少存储一份离线副本。定期测试你的恢复流程。

数据库备份

docker exec -t immich_postgres pg_dump \
  --clean --if-exists \
  --dbname=immich \
  --username=postgres | gzip > /opt/immich/backups/db-$(date +%Y%m%d-%H%M%S).sql.gz

上传目录备份

rsync -a --info=progress2 \
  /opt/immich/data/ \
  /mnt/backup/immich-data/

跳过缩略图和转码视频以节省空间。它们会自动重新生成:

rsync -a --info=progress2 \
  --exclude='thumbs/' \
  --exclude='encoded-video/' \
  /opt/immich/data/ \
  /mnt/backup/immich-data/

使用cron自动化

创建备份脚本:

cat > /opt/immich/backup.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/mnt/backup/immich"
mkdir -p "$BACKUP_DIR/db"

# Database dump
docker exec -t immich_postgres pg_dump \
  --clean --if-exists \
  --dbname=immich \
  --username=postgres | gzip > "$BACKUP_DIR/db/immich-db-$(date +%Y%m%d).sql.gz"

# Keep last 14 database dumps
find "$BACKUP_DIR/db" -name "immich-db-*.sql.gz" -mtime +14 -delete

# Media sync (excludes regeneratable data)
rsync -a \
  --exclude='thumbs/' \
  --exclude='encoded-video/' \
  /opt/immich/data/ \
  "$BACKUP_DIR/media/"

echo "Backup completed: $(date)"
SCRIPT

chmod 700 /opt/immich/backup.sh

计划每晚3:00运行(在Immich自身的内部备份2:00之后):

(crontab -l 2>/dev/null; echo "0 3 * * * /opt/immich/backup.sh >> /var/log/immich-backup.log 2>&1") | crontab -

异地备份

要实现真正的3-2-1备份策略,使用rclone将备份复制到任何S3兼容的存储提供商:

rclone sync /mnt/backup/immich remote:immich-backup --transfers 4

有关rclone配置,请参阅你的存储提供商文档。

测试恢复

没有测试过的备份不算备份。以下是恢复流程:

cd /opt/immich
docker compose down -v
rm -rf /opt/immich/postgres/*

docker compose pull
docker compose create
docker start immich_postgres
sleep 10

gunzip --stdout /mnt/backup/immich/db/immich-db-20260320.sql.gz | \
  sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | \
  docker exec -i immich_postgres psql \
    --dbname=immich \
    --username=postgres \
    --single-transaction \
    --set ON_ERROR_STOP=on

docker compose up -d

恢复后,登录Web界面。浏览你的时间线,确认照片能加载。如果缩略图缺失(因为你在备份中排除了它们),进入Administration > Job Queues并运行Generate Thumbnails

如何安全地更新Immich?

Immich遵循语义版本控制并频繁发布。某些版本包含数据库迁移。升级后不支持降级。这意味着你需要一个规范的更新流程。

逐步更新流程

  1. **阅读更新日志。**在拉取任何内容之前,检查发布页面是否有破坏性变更。

  2. **备份数据库。**运行备份脚本或如上所示的手动pg_dump。不要跳过这一步。

  3. **更新版本锁定。**编辑/opt/immich/.env并将IMMICH_VERSION改为新版本:

# Example: updating from v2.6.1 to v2.7.0
sed -i 's/IMMICH_VERSION=v2.6.1/IMMICH_VERSION=v2.7.0/' /opt/immich/.env
  1. 拉取并重启。
cd /opt/immich
docker compose pull
docker compose up -d
  1. 检查日志。
docker compose logs -f --tail=100

查找迁移消息并确认服务器无错误启动。数据库迁移在启动时自动运行。

  1. 清理旧镜像。
docker image prune -f

回滚

如果出现问题,你不能简单地切换回版本号,因为数据库可能已经迁移。应该从更新前的数据库备份恢复:

  1. 停止堆栈:docker compose down -v
  2. 从备份恢复数据库(参见上方恢复章节)
  3. .env中将IMMICH_VERSION改回之前的版本
  4. 启动堆栈:docker compose up -d

这就是为什么每次更新前都要备份。

如何将Google Takeout的照片导入Immich?

导入Google Photos导出最简单的方法是使用immich-go,这是一个社区工具,可以直接读取Google Takeout的ZIP文件。它保留相册结构、日期和JSON sidecar文件中的GPS元数据。

在你的本地机器(不是VPS)上下载immich-go。在Immich中生成API密钥:点击你的头像 > Account Settings > API Keys > New API Key

immich-go upload from-google-photos \
  --server=https://photos.example.com \
  --api-key=your-api-key \
  /path/to/takeout-*.zip

先运行dry run查看将导入的内容:

immich-go upload from-google-photos \
  --server=https://photos.example.com \
  --api-key=your-api-key \
  --dry-run \
  /path/to/takeout-*.zip

该工具通过校验和去重。如果你运行两次导入,不会重复上传任何内容。

对于较小的导入(少于几千张照片),使用Immich Web界面。将文件拖放到时间线视图中或点击上传按钮。Web上传器可以处理批量导入,但对于大型库比immich-go慢。

外部库挂载

如果你的照片已经在VPS文件系统上(来自之前的备份或迁移),你可以将它们作为外部库挂载,而不是重新上传。在docker-compose.ymlimmich-server下添加路径作为卷:

    volumes:
      - ${UPLOAD_LOCATION}:/data
      - /etc/localtime:/etc/localtime:ro
      - /mnt/photos:/mnt/photos:ro

然后在Immich Web界面中,进入Administration > External Libraries并添加/mnt/photos作为库路径。Immich就地索引文件而不复制它们。:ro标志使挂载为只读,防止Immich修改你的原始文件。

故障排查

容器不断重启

检查特定容器的日志:

docker compose logs immich-server --tail=50
docker compose logs database --tail=50

常见原因:.env中的DB_PASSWORD错误(容器和数据库不一致)、内存不足(OOM killer)或磁盘已满。

上传静默失败

几乎总是反向代理的请求体大小限制问题。检查代理配置中的client_max_body_size(Nginx)或等效设置。Caddy没有默认限制。Traefik的默认值因版本而异。

ML模型加载失败

ML容器在首次启动时下载模型。如果你的VPS带宽有限或下载被中断,模型可能已损坏。删除容器,清除模型缓存卷并重新创建:

docker compose rm -sf immich-machine-learning
docker volume rm immich_model-cache
docker compose up -d immich-machine-learning

照片显示但缩略图损坏

从管理面板重新生成缩略图:Administration > Job Queues > Generate Thumbnails > Start

恢复后的数据库连接错误

如果在恢复期间看到relation already existsforeign key constraint violated错误,说明在导入前数据库没有完全清理。停止所有容器,删除DB_DATA_LOCATION目录,重新创建postgres容器,等待10秒初始化,然后重新运行恢复。

查看日志

所有Immich日志通过Docker的日志驱动程序输出:

docker compose logs -f

查看特定服务:

docker compose logs database -f
docker compose logs immich-machine-learning -f

只过滤错误:

docker compose logs immich-server 2>&1 | grep -i error

版权所有 2026 Virtua.Cloud。保留所有权利。 本内容为 Virtua.Cloud 团队原创作品。 未经书面许可,禁止复制、转载或再分发。

准备好亲自尝试了吗?

几秒内部署您自己的服务器。支持 Linux、Windows 或 FreeBSD。

查看 VPS 方案