Featured image of post Self-Hosting Google Photos with Immich on RHEL and Podman

Self-Hosting Google Photos with Immich on RHEL and Podman

Turning an old laptop into a private photo backup with Immich, rootless , SELinux, and Tailscale on RHEL

Introductions

I wanted to set up a simple self-hosted photo backup server with Immich on RHEL.

Most Immich guides are written for setups that do not have to deal with RHEL-specific SELinux behavior. The installation itself is not too bad, but getting the permissions right can be frustrating if you have never done it before.

This post walks through how to run Immich on RHEL with rootless Podman and Tailscale, with the extra steps needed to make it work cleanly.

1. Download the Immich Files (Default Setup)

First, create the working directory and pull down the official Immich deployment files.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Create the directory for Immich
mkdir ./immich-app

# Navigate into the directory
cd ./immich-app

# Download the docker-compose.yml file
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml

# Download the example .env file
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env

2. Install podman-compose

Now we need a way to run this compose file. RHEL keeps its official repositories incredbly strict for enterprise stability. To get community-standard tools like podman-compose, we have to switch to root, enable CodeReady Builder (CRB), and get into Fedora’s Extra Packages for Enterprise Linux (EPEL).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Switch to root
su -

# Enable CRB and install EPEL
subscription-manager repos --enable codeready-builder-for-rhel-10-$(arch)-rpms
dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
logout

# Install podman-compose
sudo dnf install podman-compose

Tip: If the CRB repository does not work, you can always bypass it by installing pip and installing podman-compose through Python instead.

1
2
sudo dnf install python3-pip
pip3 install --user podman-compose

3. Edit SELinux Contexts

Next, edit the docker-compose.yml file. Because RHEL uses SELinux, it blocks containers from touching host files. We have to explicitly add permissions to the volume mounts inside the file.

1
vim docker-compose.yml
1
2
3
4
5
6
7
8
 19     volumes:
 20       # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in th    e .env file
 21       - ${UPLOAD_LOCATION}:/data:z
 22       - /etc/localtime:/etc/localtime:ro
 -
 67     volumes:
 68       # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION i    n the .env file
 69       - ${DB_DATA_LOCATION}:/var/lib/postgresql/data:z

When you are defining your volume paths in the compose file, append the correct SELinux labels:

  • Add a lowercase :z to your photo uploads and database volume folder so multiple Immich containers can share that directory without locking each other out.

4. Fire It Up

With the files configured, start the stack in detached mode.

1
podman-compose up -d

5. Fix PostgreSQL Permissions for Rootless Podman

If the database container fails with permission errors under rootless Podman, fix the ownership on the host and restart the stack. Because we are running this securely as a normal user instead of root, the host machine doesn’t recognize the container’s internal PostgreSQL user. We have to translate those permissions.

1
podman unshare chown -R 999:999 ./postgres/

The podman unshare command drops us into the container’s user namespace. It maps the container’s internal database user to a large, unprivileged user ID on the host machine, fixing the permission-denied errors without compromising security.

6. Secure Remote Access with Tailscale

The server is running, but how do you access it securely without exposing ports to the internet? Tailscale makes this effortless.

Install Tailscale using their official script:

1
curl -fsSL https://tailscale.com/install.sh | sh

Once installed, bring the node online:

1
sudo tailscale up

Copy the authentication link provided in the terminal, paste it into your browser, and authenticate. That’s it. Anything connected to your Tailscale mesh network can now access your new Immich instance securely.

Convert Containers Into a Service with Quadlet

If you want to convert your containers into a proper user service, follow these steps. With the following steps, you can have immich auto start if the system restarts and so on.

7. Transition to Quadlet (Kubernetes Style)

We can use Quadlet to treat the containers as a native systemd service. First, generate a Kubernetes YAML blueprint from the stack.

1
2
3
4
5
6
# Start the stack once to group services into a Pod
podman-compose up -d

# Export the running Pod to a K8s manifest
podman kube generate pod_immich > ~/immich-app/immich.yaml
podman-compose down

8. Networking in a Pod

Inside a Pod, containers share the same network stack. That means Immich needs to resolve database and redis to 127.0.0.1. Add this to immich.yaml.

1
2
3
4
5
6
spec:
  hostAliases:
  - ip: "127.0.0.1"
    hostnames:
    - "database"
    - "redis"

9. Configure Quadlet

Move your manifest to the systemd generator directory and create the .kube file.

1
2
3
4
5
mkdir -p ~/.config/containers/systemd/
mv ~/immich-app/immich.yaml ~/.config/containers/systemd/

# Create the service definition
vim ~/.config/containers/systemd/immich.kube

immich.kube content:

1
2
3
4
5
6
7
8
9
[Unit]
Description=Immich Kubernetes Pod Quadlet
After=network-online.target

[Kube]
Yaml=immich.yaml

[Install]
WantedBy=default.target

10. Enable and Boot

Enable lingering so the service keeps running even when you are not logged in, then start it.

1
2
3
sudo loginctl enable-linger $USER
systemctl --user daemon-reload
systemctl --user start immich.service

11. Verification

Check logs with:

1
2
journalctl --user -xeu immich.service
systemctl --user status immich.service

If you see database system is ready, the service is up.

12. Safe Upgrade (If You Followed This Guide)

This flow is designed to avoid breaking your rootless Podman + Quadlet setup and keep your database safe.

  1. Check the Immich release notes for breaking changes.
  2. Stop the running service.
  3. Pull the latest images.
  4. Regenerate the Pod spec if the compose file changed.
  5. Re-apply permissions if the database fails to start.

1) Read the release notes

Go to the Immich GitHub releases page and scan for any migration notes or changes to environment variables.

2) Stop the running service

1
systemctl --user stop immich.service

3) Pull the latest images

1
podman-compose pull

4) Regenerate the Pod (if the compose file changed)

If you updated docker-compose.yml or .env, regenerate the manifest and re-apply the Quadlet file.

1
2
3
4
podman-compose up -d
podman kube generate pod_immich > ~/immich-app/immich.yaml
podman-compose down
systemctl --user daemon-reload

5) Start the service

1
systemctl --user start immich.service

6) Fix database permissions if needed

If the database fails to start due to permission errors, re-apply the rootless ownership mapping and restart.

1
2
podman unshare chown -R 999:999 ./postgres/
systemctl --user restart immich.service

7) Verify

1
2
journalctl --user -xeu immich.service
systemctl --user status immich.service
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy