Self-hosted photo and video backup and management server with web UI, mobile sync, and shared albums.
| Port | 2283 |
| Registry | ghcr.io/daemonless/immich-server |
| Source | https://github.com/immich-app/immich |
| Website | https://immich.app/ |
| Tag | Description | Best For |
|---|---|---|
latest |
Upstream Binary. Built from official release. | Most users — recommended. |
beta |
Beta release built from upstream v3.2.0-rc.1. | Alternative build. |
Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions.
services:
immich-server:
image: "ghcr.io/daemonless/immich-server:latest"
container_name: immich-server
environment:
- DB_HOSTNAME=immich-postgres # Postgres database hostname
- DB_USERNAME=postgres # Postgres database user
- DB_PASSWORD=postgres # Postgres database password
- DB_DATABASE_NAME=immich # Postgres database name
- REDIS_HOSTNAME=immich-redis # Redis hostname
- PUID=1000 # User ID for the application process
- PGID=1000 # Group ID for the application process
- TZ=UTC # Timezone for the container
- SKIP_CHOWN=true # Skip the startup recursive chown of /config and /data once ownership is recorded in /config/.chown_done (default true). Set false to force a chown on every start. The marker lives in /config, so /config must be a persistent volume for the skip to take effect across restarts.
volumes:
- "/path/to/containers/immich-server:/config"
- "/path/to/containers/immich-server/data:/data"
ports:
- "2283:2283"
# always (not unless-stopped) so FreeBSD's podman rc.d auto-starts it at boot
restart: alwaysSave as compose.yaml, then run podman-compose up -d.
.env:
# .env
DIRECTOR_PROJECT=immich-server
DB_HOSTNAME=immich-postgres
DB_USERNAME=postgres
DB_PASSWORD=postgres
DB_DATABASE_NAME=immich
REDIS_HOSTNAME=immich-redis
PUID=1000
PGID=1000
TZ=UTC
SKIP_CHOWN=true
appjail-director.yml:
# appjail-director.yml
options:
- virtualnet: ':<random> default'
- nat:
services:
immich-server:
name: immich_server
options:
- container: 'boot args:--pull'
- expose: '2283:2283 proto:tcp'
oci:
user: root
environment:
- DB_HOSTNAME: !ENV '${DB_HOSTNAME}'
- DB_USERNAME: !ENV '${DB_USERNAME}'
- DB_PASSWORD: !ENV '${DB_PASSWORD}'
- DB_DATABASE_NAME: !ENV '${DB_DATABASE_NAME}'
- REDIS_HOSTNAME: !ENV '${REDIS_HOSTNAME}'
- PUID: !ENV '${PUID}'
- PGID: !ENV '${PGID}'
- TZ: !ENV '${TZ}'
- SKIP_CHOWN: !ENV '${SKIP_CHOWN}'
volumes:
- immich-server: /config
- immich-server_data: /data
volumes:
immich-server:
device: '/path/to/containers/immich-server'
immich-server_data:
device: '/path/to/containers/immich-server/data'Makejail:
# Makejail
ARG tag=latest
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/immich-server:${tag}
Save the files above, then run appjail-director up.
Note: Exposing ports in AppJail means that your service can be reached from remote hosts. If that is not your intention, do not expose the ports and communicate with the service using the IPv4 address assigned by the virtual network.
podman run -d --name immich-server \
-p 2283:2283 \
-e DB_HOSTNAME=immich-postgres \
-e DB_USERNAME=postgres \
-e DB_PASSWORD=postgres \
-e DB_DATABASE_NAME=immich \
-e REDIS_HOSTNAME=immich-redis \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=UTC \
-e SKIP_CHOWN=true \
-v /path/to/containers/immich-server:/config \
-v /path/to/containers/immich-server/data:/data \
ghcr.io/daemonless/immich-server:latestSave as run.sh, then run sh run.sh.
appjail oci run -Pd \
-o overwrite=force \
-o container="args:--pull" \
-o virtualnet=":<random> default" \
-o nat \
-o expose="2283:2283 proto:tcp" \
-e DB_HOSTNAME=immich-postgres \
-e DB_USERNAME=postgres \
-e DB_PASSWORD=postgres \
-e DB_DATABASE_NAME=immich \
-e REDIS_HOSTNAME=immich-redis \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=UTC \
-e SKIP_CHOWN=true \
-o fstab="/path/to/containers/immich-server /config <pseudofs>" \
-o fstab="/path/to/containers/immich-server/data /data <pseudofs>" \
ghcr.io/daemonless/immich-server:latest immich-serverSave as run.sh, then run sh run.sh.
Note: Exposing ports in AppJail means that your service can be reached from remote hosts. If that is not your intention, do not expose the ports and communicate with the service using the IPv4 address assigned by the virtual network.
Warning
Bastille's OCI support is experimental. It requires buildah, shares the host network stack (inherit), and persists image-declared volumes under --data-path.
services:
immich-server:
image: "ghcr.io/daemonless/immich-server:latest"
container_name: immich-server
network_mode: host # jail shares host networking
environment:
- DB_HOSTNAME=immich-postgres
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
- DB_DATABASE_NAME=immich
- REDIS_HOSTNAME=immich-redis
- PUID=1000
- PGID=1000
- TZ=UTC
- SKIP_CHOWN=trueSave as podman-compose.yml, then run bastille up. Or via CLI:
bastille create -O \
--env DB_HOSTNAME=immich-postgres \
--env DB_USERNAME=postgres \
--env DB_PASSWORD=postgres \
--env DB_DATABASE_NAME=immich \
--env REDIS_HOSTNAME=immich-redis \
--env PUID=1000 \
--env PGID=1000 \
--env TZ=UTC \
--env SKIP_CHOWN=true \
--data-path /path/to/containers/immich-server \
immich-server ghcr.io/daemonless/immich-server:latest inherit- name: Deploy immich-server
containers.podman.podman_container:
name: immich-server
image: "ghcr.io/daemonless/immich-server:latest"
state: started
restart_policy: always
env:
DB_HOSTNAME: "immich-postgres"
DB_USERNAME: "postgres"
DB_PASSWORD: "postgres"
DB_DATABASE_NAME: "immich"
REDIS_HOSTNAME: "immich-redis"
PUID: "1000"
PGID: "1000"
TZ: "UTC"
SKIP_CHOWN: "true"
ports:
- "2283:2283"
volumes:
- "/path/to/containers/immich-server:/config"
- "/path/to/containers/immich-server/data:/data"Save as immich-server-deploy.yaml, then run ansible-playbook immich-server-deploy.yaml.
| Variable | Default | Description |
|---|---|---|
DB_HOSTNAME |
immich-postgres |
Postgres database hostname |
DB_USERNAME |
postgres |
Postgres database user |
DB_PASSWORD |
postgres |
Postgres database password |
DB_DATABASE_NAME |
immich |
Postgres database name |
REDIS_HOSTNAME |
immich-redis |
Redis hostname |
PUID |
1000 |
User ID for the application process |
PGID |
1000 |
Group ID for the application process |
TZ |
UTC |
Timezone for the container |
SKIP_CHOWN |
true |
Skip the startup recursive chown of /config and /data once ownership is recorded in /config/.chown_done (default true). Set false to force a chown on every start. The marker lives in /config, so /config must be a persistent volume for the skip to take effect across restarts. |
| Path | Description |
|---|---|
/config |
Holds the startup ownership marker (.chown_done). Mount as a persistent volume so the chown is skipped on later starts. |
/data |
Media storage (photos, videos, thumbnails) |
| Port | Protocol | Description |
|---|---|---|
2283 |
TCP | Web UI/API |
This image is part of the Immich Stack.
Architectures: amd64
User: bsd (UID/GID via PUID/PGID, defaults to 1000:1000)
Base: FreeBSD 15.1
Need help? Join our Discord community.