Using a read-only SMB share in a root-less Immich setup with SELinux enabled

I recently wanted to switch from Google Photos to Immich and while doing so I stumbled across some difficulties while adding the photo’s on my NAS as an external library. In the past 20+ years I organized my library by hand without relying on any tools, so I did not want Immich to make any changes to my photo library, hence I mounted the Samba share as read-only.

//nas.internal/photo /mnt/photo cifs credentials=/root/samba.cred,ro,nodev,noexec,nosuid,gid=0,dir_mode=0777,file_mode=0444 0 0

If I try to add a folder from this share as an external library I get the following error: “Lacking read permissions for folder”

Disabling SELinux would fix the issue, but even if the instance is not publicly available, it’s still a bad idea to disable any security measures. So we need to tell SELinux it’s fine for the container to access the share. Usually this is done by appending :z to the volume:

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    # extends:
    #   file: hwaccel.ml.yml
    #   service: vaapi # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
    volumes:
      # 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 the .env file
      - /var/immich/media/:/usr/src/app/upload:z
      - /etc/localtime:/etc/localtime:ro
      - /mnt/photo:/usr/src/app/external:z
    env_file:
      - immich.env
    ports:
      - '2283:2283'
    depends_on:
      - database
    restart: always
    healthcheck:
      disable: false

But simply adding “:z” in the Docker compose file won’t work for two reasons:

  1. The user does not have any root privileges to change the SELinux context
  2. The filesystem is mounted read-only and changing the context is a write operation

Luckily, we can mount the SMB share with an SELinux content which will allow the container to access the files:

//nas.internal/photo /mnt/photo cifs credentials=/root/samba.cred,ro,nodev,noexec,nosuid,gid=0,dir_mode=0777,file_mode=0444,context="system_u:object_r:container_file_t:s0" 0 0

To apply the changes we need to unmount/remount the share

# stop the immich containers first
podman compose down
# Remount (mount -o remount won't work, remount can't change permissions)
sudo umount /mnt/photo
sudo mount /mnt/photo

If we now restart Immich and add the Samba share we can see that it can access the files:

Adding Elastichsearch filebeat to Docker images

One of the projects I’m working on uses a micro-service architecture. Every micro-service is shipped as a Docker image and executed on Amazon AWS using ECS. This basically means that on a random number of servers, a (somewhat) random number of instances of a single micro-service can run … and this about 15 times (the number of micro-services we have).

In the best case this means that 15 Docker containers are running on 4 EC2 instances and thus there are 15 logs files to gather. To handle this we use the Elastichsearch ELK stack using ElasticSearch, Logstash and Kibana. Our micro-services do not directly connect to the Logstash server, instead we use filebeat to read the logfile and send it to Logstash for parsing (as such, the load of processing the logs is moved to the Logstash server).

Filebeat configuration

Filebeat is configured using YAML files, the following is a basic configuration which uses secured connection to logstash (using certificates). Using the fields property we can injection additional parameters like the environment and the application (in this case the micro-service’s name). The multi-line pattern will make sure that stacktraces are sent as one line to the server.

In this case the filebeat server will monitor the /tmp/application*.log files (which is where we log or logs)

filebeat.prospectors:
- input_type: log
  paths:
    - /tmp/application*.log
  document_type: logback  
  multiline.pattern: ^\d\d\d\d
  multiline.negate: true
  multiline.match: after
fields:
  env: %ENVIR%
  app: %APP%
output.logstash:
  hosts: ["%STASH%"]
  ssl.certificate_authorities: ["/app/ca.pem"]
  ssl.certificate: "/app/cert.pem"
  ssl.key: "/app/key.pem"

 

The %ENVIR%, %APP% and %STASH% parts will be replace later so that we can customize the logging for each environment and micro-service.

Installing filebeat in a Docker image

This is actually pretty straight forward. The following Dockerfile will install the filebeat service and on startup of the container it will run entrypoint.sh with the command of your likings.

FROM openjdk:8u111-jdk
#
#
# Stuff to get the micro-service running (this is not important for this blog)
#
#

# Monitoring
RUN wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-5.2.0-amd64.deb
RUN dpkg -i filebeat-5.2.0-amd64.deb
RUN rm filebeat-5.2.0-amd64.deb

COPY filebeat.yml /etc/filebeat/filebeat.yml
COPY entrypoint.sh /bin/entrypoint.sh
RUN chmod +x /bin/entrypoint.sh

ENV APP unk
ENV ENVIR unk
ENV STASH your-server:5044
#because we have a secured connection to logstash
COPY ca.pem /app/ca.pem
COPY cert.pem /app/cert.pem
COPY key.pem /app/key.pem

CMD /bin/entrypoint.sh "java -jar /app/service.jar -server --spring.profiles.active=${PROFILE}"

A couple of notes: APP and ENVIR are configure to ‘unk’ here because it’s a base image for other micro-services. Each micro-service will have it’s own Dockerfile which extends this image where it configures the APP variable. The ENVIR variable will be set during startup of the container by passing it as a variable.

Starting the service

Because Docker images don’t contain running service, we need to start the service, that’s why we need the entrypoint.sh file. This script will also alter the filebeat.yml file so that it’s configured with information about the environment and the micro-service.

#!/usr/bin/env bash
sed -i -e s/%STASH%/$STASH/g /etc/filebeat/filebeat.yml
sed -i -e s/%APP%/$APP/g /etc/filebeat/filebeat.yml
sed -i -e s/%ENVIR%/$ENVIR/g /etc/filebeat/filebeat.yml
service filebeat start
echo "$*"
/bin/sh -c "$*"