Searx - using my own Caddy

Searx - using my own Caddy

The Searx-docker quick start does a couple of things that I understand, but don't really like. One is cloning the repo to get things set up & the other is hard coding the reverse proxy into the docker-compose file (even if it is Caddy, which I use already).
It's not a great idea to expose a Searx installation to the outside without some of the protections that come baked into that hard coded setup though, so I did some searching.
Turns out that using Filtron to reach Searx (& using the filtering rules that are in that github repo) is a good idea, but there's no need for the Caddy proxy that they baked in.
So, the aim is to get a Caddy instance that's already running to kick off the Searx container, via Filtron's filtering, without running anything more than I need to.

Step 1 - (re)write the docker-compose file

version: "2"

services:

  filtron:
    container_name: filtron
    image: dalf/filtron
    restart: always
    ports:
      - 4040:4040 
      - 4041:4041 
    networks:
      - searx
    command: -listen 0.0.0.0:4040 -api 0.0.0.0:4041 -target searx:8080 
    volumes:
      - ~/docker/searx/filtron/rules.json:/etc/filtron/rules.json:rw
    read_only: true
    cap_drop:
      - ALL

  searx:
    container_name: searx
    image: searx/searx:latest
    restart: always
    networks:
      - searx
#    command: ${SEARX_COMMAND:-} #comment this line if you made / will make some modifications to the settings
    volumes:
      - ~/docker/searx:/etc/searx:rw
    environment:
      - BIND_ADDRESS=0.0.0.0:8080 #10137
      - BASE_URL=${MY_SEARX_URL}
      - MORTY_URL=${MY_SEARX_URL}/morty/
      - MORTY_KEY=${MORTY_KEY}
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
      - DAC_OVERRIDE

  morty:
    container_name: morty
    image: dalf/morty
    restart: always
    ports:
      - 3000:3000
    networks:
      - searx
    command: -timeout 6 -ipv6
    environment:
      - MORTY_KEY=${MORTY_KEY}
      - MORTY_ADDRESS=0.0.0.0:3000
    logging:
      driver: none
    read_only: true
    cap_drop:
      - ALL

networks:
  searx:
    ipam:
      driver: default

You'll note that there are a couple of places in there where you could either hard code or use an environment variable to set things up.
${MORTY_KEY} should be created as suggested in the github site (or do something yourself!)
${MY_SEARX_URL} would be something like https://searx/mydomain.com (& you'll need a https://searx/mydomain.com/morty too).

Step 2 - Get Filtron Ready

No point in having a filtering proxy if there are no rules to go with it.
You'll need to create the rules file at "~/docker/searx/filtron/rules.json" (Assuming that's the place that you specified in the docker-compose!)

nano ~/docker/searx/filtron/rules.json

Then just paste in the rules. (Be careful that you copy all of the brackets at the start & end (ask me how i know!)).

Step 3 - set up Caddy

Mainly this was a matter of adding the rules to the bottom of my Caddyfile & making them associate with the subdomain that I use for Searx (${MY_SEARX_URL}  in the docker-compose file above). I've used that format for the reference in the snippet below, you'll need to do a search & replace in order to use it for your version. N.B. If you use an internal IP address then all of the ${MY_SEARX_URL} after that first one (on line 1) will need to be the IP address of the machine running your docker containers!

${MY_SEARX_URL} {
        log {
                output file /config/searx_${MY_SEARX_URL}.txt {
                        roll_size 10mb
                        roll_keep 3
                        roll_keep_for 2160h
                }
        }
        @api {
                path /config
                path /status
        }

        @static {
                path /static/*
        }

        @notstatic {
                not path /static/*
        }

        @morty {
                path /morty/*
        }

        @notmorty {
                not path /morty/*
        }

        header {
                # Enable HTTP Strict Transport Security (HSTS) to force clients                                                                              to always connect via HTTPS
                Strict-Transport-Security "max-age=31536000; includeSubDomains;                                                                              preload"

                # Enable cross-site filter (XSS) and tell browser to block detec                                                                             ted attacks
                X-XSS-Protection "1; mode=block"

                # Prevent some browsers from MIME-sniffing a response away from                                                                              the declared Content-Type
                X-Content-Type-Options "nosniff"

                # Disallow the site to be rendered within a frame (clickjacking                                                                              protection)
                X-Frame-Options "SAMEORIGIN"

                # Disable some features
                Permissions-Policy "accelerometer=();ambient-light-sensor=(); au                                                                             toplay=();camera=();encrypted-media=();focus-without-user-activation=(); geoloca                                                                             tion=();gyroscope=();magnetometer=();microphone=();midi=();payment=();picture-in                                                                             -picture=(); speaker=();sync-xhr=();usb=();vr=()"

                # Disable some features (legacy)
                Feature-Policy "accelerometer 'none';ambient-light-sensor 'none'                                                                             ; autoplay 'none';camera 'none';encrypted-media 'none';focus-without-user-activa                                                                             tion 'none'; geolocation 'none';gyroscope 'none';magnetometer 'none';microphone                                                                              'none';midi 'none';payment 'none';picture-in-picture 'none'; speaker 'none';sync                                                                             -xhr 'none';usb 'none';vr 'none'"

                # Referer
                Referrer-Policy "no-referrer"

                # X-Robots-Tag
                X-Robots-Tag "noindex, noarchive, nofollow"

                # Remove Server header
                -Server
        }

        header @api {
                Access-Control-Allow-Methods "GET, OPTIONS"
                Access-Control-Allow-Origin "*"
        }

        # Cache
        header @static {
                # Cache
                Cache-Control "public, max-age=31536000"
                defer
        }

        header @notstatic {
                # No Cache
                Cache-Control "no-cache, no-store"
                Pragma "no-cache"
        }

        # CSP (see http://content-security-policy.com/ )
        header @morty {
                Content-Security-Policy "default-src 'none'; style-src 'self' 'u                                                                             nsafe-inline'; form-action 'self'; frame-ancestors 'self'; base-uri 'self'; img-                                                                             src 'self' data:; font-src 'self'; frame-src 'self'"
        }

        header @notmorty {
                Content-Security-Policy "upgrade-insecure-requests; default-src                                                                              'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; form-action 'self';                                                                              font-src 'self'; frame-ancestors 'self'; base-uri 'self'; connect-src 'self' ht                                                                             tps://overpass-api.de; img-src 'self' data: https://*.tile.openstreetmap.org; fr                                                                             ame-src https://www.youtube-nocookie.com https://player.vimeo.com https://www.da                                                                             ilymotion.com https://www.deezer.com https://www.mixcloud.com https://w.soundclo                                                                             ud.com https://embed.spotify.com"
        }

        # Morty
        handle @morty {
                reverse_proxy ${MY_SEARX_URL}:3000
        }

        # Filtron
        handle {
                encode zstd gzip

                reverse_proxy ${MY_SEARX_URL}:4040 {
                        header_up X-Forwarded-Port {http.request.port}
                        header_up X-Forwarded-Proto {http.request.scheme}
                        header_up X-Forwarded-TlsProto {tls_protocol}
                        header_up X-Forwarded-TlsCipher {tls_cipher}
                        header_up X-Forwarded-HttpsProto {proto}
                }
        }

Go Go Go

So, we've got the Caddy config set up, to point to the filtron instance, which was started with the docker-compose that we created in step 1. All we now need to do is create/update the CNAME for ${MY_SEARX_URL}, reload the Caddyfile (to update Caddy's instructions & to get the https certificate to go with ${MY_SEARX_URL} ) then run the docker-compose (or run it as a stack from portainer) & we're good to go!
You can test it from outside of your network, but don't forget to update your internal DNS settings if you want to use the ${MY_SEARX_URL}  from inside your network (or just use the IP address & port of the server)