在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-server和immich-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)并使用管理员凭据登录。应用会提示你启用自动备份。
配置自动备份
- 打开应用,点击右上角的头像,然后点击Backup Settings。
- 启用Background Backup。在iOS上,还要在系统设置中启用Background App Refresh。在Android上,为Immich禁用电池优化,防止系统杀死它。
- 选择要备份的相册。默认情况下,Immich备份你的相机胶卷。你可以从相册选择界面添加其他相册(截图、WhatsApp图片)。
- 仅当你的手机套餐允许时才启用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遵循语义版本控制并频繁发布。某些版本包含数据库迁移。升级后不支持降级。这意味着你需要一个规范的更新流程。
逐步更新流程
-
**阅读更新日志。**在拉取任何内容之前,检查发布页面是否有破坏性变更。
-
**备份数据库。**运行备份脚本或如上所示的手动
pg_dump。不要跳过这一步。 -
**更新版本锁定。**编辑
/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
- 拉取并重启。
cd /opt/immich
docker compose pull
docker compose up -d
- 检查日志。
docker compose logs -f --tail=100
查找迁移消息并确认服务器无错误启动。数据库迁移在启动时自动运行。
- 清理旧镜像。
docker image prune -f
回滚
如果出现问题,你不能简单地切换回版本号,因为数据库可能已经迁移。应该从更新前的数据库备份恢复:
- 停止堆栈:
docker compose down -v - 从备份恢复数据库(参见上方恢复章节)
- 在
.env中将IMMICH_VERSION改回之前的版本 - 启动堆栈:
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.yml的immich-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 exists或foreign 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 团队原创作品。 未经书面许可,禁止复制、转载或再分发。