Immich and Synology Photos – Access from outside – Part 1/4

Background

I have done some posts in the past about photos. I suppose I am not the only one with tons of videos and photos. I started to save on harddrives/usb, Started using Picassa and really liked it but Google decided to end it in 2016. The alternative was Google Photos but I never liked it. Plus the fact that it would soon cost a lot of money. The solution was a Synology nas with Photo Station. There were some features like editing geo tags that were quite handy. However Synology started to roll out Moments. It was quite a disappointment. After a while Moments disapeared and Synology Photos appeared. I have been using it for a while now.

This post was originally meant as a guide how to setup Immich. However there were so many flaws with my old reverse proxy setup so I decided to redo everything from scratch. So in this first post we will focus on getting the components up and running and make sure we have what we need.

Synology Photos

Pros

  • Stable
  • Runs on your NAS
  • AI with more RAM
  • Some nice features like conditional albums. Tags. Mobile sync

Cons

  • Slow development
  • Poor features for geo tagging
  • Videos vs Photos is confusing
  • Adding many users is complicated

I have also tried some other alternatives. There is a good comparison chart here if you want to try out more.

PhotoPrism

It is quite alright but I think it lacks in development.

Piwigo

This is something else. I am using it right now for sharing photos to external people as this does not work very well with Synology. It has some nice features like adding albums and the frontend looks ok. However it feels old especially in the back end. It is more like a gallery or portfolio site.

Prerequirements

So this post will be about setting up Immich and Photos. The future looks bright for Immich and the fact that they recently joined Futo makes development more future safe. In order to have this setup as good and as secure as possible there are some steps and configuration we will have to do. All these steps are optional. You could just run Immich on-prem if you want to. You will need:

  • Linux (Ubuntu) server
  • Public WAN. Non CGNAT 
  • Open/NAT 80/443 on your router
  • Avarage Linux skills
  • Patience
  • A domain name (or Duckdns)

Goal

  • Have a stable Immich up and running
  • Be able to continue to use Synology Photos
  • Add my current Synology Shared drives as external libraries
  • Use both photos and videos
  • Sync mobile photos
  • Add family users as admins
  • Access Immich from Outside
  • Be able to share external both from Photos and Immich
  • Be able to have backups
  • Secure and harden the setup with firewall rules and MFA

Components

So lets get started. As this post is quite long you might not always understand why we do certain thing. Sometimes we do something first that may apply later. So it might be a good idea to read everything first and then start over.

Docker Compose

To make all this work you will have to install Docker and Docker compose. I have an Intel Nuc running Ubuntu hosting my containers. Setting up Docker may vary depending of your OS. Make sure this is working before proceeding. One mistake I did first was to put all containers in one docker-compose.yaml file. Later I created a separate file in another dir with minecraft containers. This will make updates and auto restarts easier to handle.  I would also like to mention that Docker can be quite complicated. There are many parameters used for network and security. I might use parameters in a wrong way so make sure to verify everything.

Before you run your containers with “sudo docker-compose up -d” make sure that you have created the correct directories.

Portainer

I run Portainer as well. You do not have to install it but it has helped me a lot of restarting containers and analyzing the log console. Note that I save my files in /srv/docker/portainer so you will have to create this directory. You can access portainer via yourinternalip:9000

  portainer:
    container_name: portainer
    image: portainer/portainer-ce
    restart: unless-stopped
    volumes:
      - /srv/docker/portainer:/data
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - "9000:9000"

Swag

What is Swag? According to themself it is

SWAG – Secure Web Application Gateway (formerly known as letsencrypt, no relation to Let’s Encrypt™) sets up an Nginx webserver and reverse proxy with php support and a built-in certbot client that automates free SSL server certificate generation and renewal processes (Let’s Encrypt and ZeroSSL). It also contains fail2ban for intrusion prevention.

The reason we want to use Swag is to be able to access Immich remote via https and be able to do some basic hardening.

First create the docker-compose.yaml file

sudo nano docker-compose.yaml

Now paste something like this. Three lines are # but will enable them later

  swag:
    image: lscr.io/linuxserver/swag:latest
    container_name: swag
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Stockholm
      - URL=mydomain.org
      - VALIDATION=dns
      - SUBDOMAINS=wildcard
      - DNSPLUGIN=cloudflare #optional
#     - DOCKER_MODS=linuxserver/mods:swag-dashboard|linuxserver/mods:swag-maxmind
#    - MAXMINDDB_LICENSE_KEY=xxxxxxx
#     - MAXMINDDB_USER_ID=myemail@mydomain.se
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /srv/docker/swag/config:/config
    ports:
      - 443:443
      - 80:80 #optional
    restart: unless-stopped

Run:

sudo docker-compose up -d

This will not work. You can see whats happening when open Portainer in the logs view. There are some parts missing like the Cloudflare API.

There are some settings I added afterwards in order to get Immich and Swag in the same Docker networks. I will put Immich composefiles in another stack later on so in order to solve some future issues I added the network shared in bridge and applied it into all my containers. So first I define a network called shared and later added the network in the swag container. You can google docker networks as this is beyound this post. ANother workaround would be to put all configs in the same docker-compose.yaml file.

version: '3'
networks:
  shared:
    driver: bridge
services:
  swag:
    image: lscr.io/linuxserver/swag:latest
    container_name: swag
    networks:
      - shared
    cap_add:
      - NET_ADMIN

Cloudflare

First make sure you have a domain name. In my case I have this domain name where my blog is hosted. However I did not want to use that. A simple domain name is about 10$ / year so just sign up for a new. At least that what I did. Now create a free Cloudflare account. Point your domain to the nameservers of Cloudflare. You should see the instructions when you open Cloudflare.

Also make sure that you have opened (NAT) 80/443 on your router to the Ip address of your server running Docker. You can disable port 80 later as this only is used for initial setup of the certificate.

Create A records pointing to your wan ip.

Generate a token under “Manage Account and Account API Tokens”. Note that it will need Edit Zone DNS. Do not use user tokens under your profile.

So if you do not have a domain and want to use something like Duckdns? The steps in this guide is for Cloudflare. I have used Duckdns before and it works fine but there are some issues. Lets say you want to share files or have a public page. Many providers or filters will block Duckdns as it is considered unsafe. For example Cisco Umbrella does this. This will make sharing photos a mess as half of the users you share with will not be able to access the site. So my advise is to skip.

We will add Maxmind later and this is a database for geoblocking. I noticed it will also block Cloudflare IPs if you are using proxied dns in Cloudflare. In general you should used proxied as this will hide your ip address connected to your domain name. If possible you do want anyone to see it. So make sure you enable this like in the image below.

I also noticed that you will have to change the SSL settings to Full.

I got some strange errors when Maxmind was enabled. However I did not want to disable it as that would expose my sites to all countries. My conclusion was that Maxmind would block sites when proxied was on and when I had enabled this in the sites-conf/mysite.conf. (I will explain this more in detail later). Depending on the case you could get a redirecting loop, 404 or something else.

So the conclusion would be

  • Use Maxmind settings when using non proxied in Cloudflare
  • Use Cloudflare WAF custom rules when using proxied.

Above you see a Cloudflare rule blocking all counties except Sweden.

You can still enable Maxmind in your configuration but just do not apply the settings per site

Back to Swag. When you have the token open up the file config/dns-conf/cloudflare.ini in swag dir and paste credentials. The file looks like this. Make sure you edit the token at the bottom.

# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20
# Replace with your values

# With global api key:
#dns_cloudflare_email = 
#dns_cloudflare_api_key = 

# With token (comment out both lines above and uncomment below):
dns_cloudflare_api_token = xxxxxxxxxxxxxxxxxxxxxx

You can restart the containers if you want to. Look in the logs if the certificate is created. If you are using wildcard domains you should be up and running. You can test your domain however there are no sites added yet. You will just see the default Swag landing page.

Hardening Swag

There are some quite easy steps you can take to improve security in Swag. Below are some steps more in detail. More info here.

Uncomment the tree lines in the docker-compose.yaml file under swag. What we want to do is

  • Add Maxmind that downloads  the geodatabase
  • Add a dashboard so we can see some alerts
  • Block countries and apply this per site
- DOCKER_MODS=linuxserver/mods:swag-dashboard|linuxserver/mods:swag-maxmind
- MAXMINDDB_LICENSE_KEY=xxxxxxxxxxxxxxxxx
- MAXMINDDB_USER_ID=<user-id>

Sign up for Maxmind and get a licensekey. Paste this with user id above. You can restart the container with

sudo docker-compose up -d

Add the following line to /config/nginx/nginx.conf under the http section:

include /swag/config/nginx/maxmind.conf;

The configuration file of Maxmind is found here. Like I wrote before it seems like it is not working when using proxied in Cloudflare. Maybe the Cloudflare is comming from a blocked country? Or maybe there is something else I have missed. However this is the general setting and it can be a good idea to enable them.

/letsencrypt/config/nginx$ maxmind.conf

geoip2 /config/geoip2db/GeoLite2-City.mmdb {
    auto_reload 1w;
    $geoip2_data_city_name   city names en;
    $geoip2_data_postal_code postal code;
    $geoip2_data_latitude    location latitude;
    $geoip2_data_longitude   location longitude;
    $geoip2_data_state_name  subdivisions 0 names en;
    $geoip2_data_state_code  subdivisions 0 iso_code;
    $geoip2_data_continent_code   continent code;
    $geoip2_data_country_iso_code country iso_code;
}

# Country Codes: https://en.wikipedia.org/wiki/ISO_3166-2

map $geoip2_data_country_iso_code $geo-whitelist {
    default no;
    # Example for whitelisting a country, comment out 'default yes;' above and uncomment 'default no;' and the whitelisted country below
    # default no;
    # UK yes;
     SE yes;
     NO yes;
     FR yes;
     CH yes;
#     BR yes;
}

map $geoip2_data_country_iso_code $geo-blacklist {
    default yes;
    # Example for blacklisting a country, uncomment the blacklisted country below
     UK no;
 }

geo $lan-ip {
    default no;
    10.0.0.0/8 yes;
    172.16.0.0/12 yes;
    192.168.0.0/16 yes;
    127.0.0.1 yes;
}

This file above is a little bit tricky. Or maybe I am just stupid. The whitelisting/blacklisting and yes/no makes it easy to do mistakes. It is very important you get this correct. Some people will tell you never to expose something to the public internet and most likely they are right. However if you want to share files with other a vpn solution is not really perfect. In this case you can block all countries except Sweden. So I have blocked 99.87% of all potential users. I feel that this is good enough. As Swag also is using fail2ban and the fact that I will be using MFA makes things better.

In order to verify that you have done the correct settings use Geopeeker or Geotagetly. By doing so we verify that your site is unreachable. Same applies when blocking via Cloudflare.

SWAG  Dashboard

Maybe you noticed we added a mod in the docker-compose file? It was the Swag Dashboard. It will help you to keep track of logins

DOCKER_MODS=linuxserver/mods:swag-dashboard

You can read more about the mod here

You will only be able to access the dashboard via https://dashboard.mydomain.something and internal. So add some dns setting in your local lan or modify your local host file and point it to your servers internal ip. If using Cloudflare proxied the statistics may be a little bit inaccurate here. However you could use their dashboard as well.

DDNS

Maybe you noticed we did put a static ip adress in the A record in Cloudflare? And maybe you read about Duckdns. In this case we would like to update the Cloudflare zones with your WAN address when it changes. In the past I have used ddns in my router with duckdns. I know some routers have the option of adding Cloudflare. However I noticed that some only work with the personal tokens. In this case we will spin up another container.

So either add your existing docker-compose.yaml or create a new folder with a new file. We will use this container. Note that you will need the “Account – Account Filter Lists” permission on yout token in order to update. See image.

Below is my docker compose settings.

  cloudflare-ddns:
    container_name: cloudflare-ddns
    image: favonia/cloudflare-ddns:latest
    network_mode: host
    restart: always
    user: "1000:1000"
    read_only: true
    cap_drop: [all]
#    security_opt: [no-new-privileges:true]
    environment:
      - CLOUDFLARE_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxx
      - DOMAINS=mydomain.org.nu,immich.mydomain.org,ha.mydomain.org,photos.mydomain.org
      - PROXIED=true
      - IP6_PROVIDER=none

Restart Docker compose. This new container should start up. I can see that it is working in my Portainer logs

So if your wan ip address changed it will be updated. It will also check if the sites are proxied.

Summarizing

I know I wrote this post was about setting up Immich. We will come to this later on. Now we have the infrastructure up and running. However we have not even set up one single site. The default swag page should however load. Try to type your domain name and see if you see this.

If you are it works. However your subdomains will not work yet. We will do this in the next post. If you do not see the page make sure you did not enable the last two lines below. That would activate geoblocking. This is if you use proxied in Cloudflare. If you do not do that you can add the lines in order to geoblock your default page. Or you could of course disable the default.

    # enable subfolder method reverse proxy confs
    include /config/nginx/proxy-confs/*.subfolder.conf;
#    if ($lan-ip = yes) { set $geo-whitelist yes; }
#    if ($geo-whitelist = no) { return 404; }