There are some Docker containers running services that I want to make sure use a privacy VPN. For example, SearXNG.

One of the main reasons to use a self-hosted meta search engine like SearXNG is to protect your privacy. SearXNG protects privacy in several ways:

  1. Doesn't send cookies to the external search engines it uses
  2. Generates a random browser profile for every search
  3. Doesn't serve ads, so no third-party tracking
  4. Hides the referring page and search query from the page you visit

However, when SearXNG pings all of the external search engines you choose, it's still using the IP address of the instance.

I use Tailscale with Mullvad exit nodes for my privacy VPN needs. I have SearXNG installed in a Docker container. Here's how to tell that container (or any container you want) to use a Mullvad exit node with docker-compose.

Basically, you're going to be adding a Tailscale sidecar to whatever container your service is in. Then you're going to tell that Tailscale sidecar to use an exit node using TS_EXTRA_ARGS=-exit-node=IP_OF_EXIT_NODE.

services:
  redis:
    container_name: redis
    image: docker.io/valkey/valkey:8-alpine
    command: valkey-server --save 30 1 --loglevel warning
    restart: unless-stopped
    network_mode: service:ts-searxng
    depends_on:
      - ts-searxng
    volumes:
      - valkey-data2:/data
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
        max-file: "1"
  
  # Here's the Tailscale sidecar
  ts-searxng:
    container_name: ts-searxng
    image: tailscale/tailscale:latest
    hostname: searxng
    restart: unless-stopped
    volumes:
      - "/volume1/docker/ts-searxng/state:/var/lib/tailscale"
    devices:
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin
      - sys_module
    environment:
      - TS_AUTH_KEY=AUTH_KEY
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=false
      - TS_EXTRA_ARGS=--exit-node=IP_OF_EXIT_NODE --exit-node-allow-lan-access=true --reset
      - TS_TAILSCALED_EXTRA_ARGS=--no-logs-no-support

  searxng:
    container_name: searxng
    image: docker.io/searxng/searxng:latest
    restart: unless-stopped
    network_mode: service:ts-searxng
    depends_on:
      - ts-searxng
    # ports:
      # - "127.0.0.1:8080:8080"
    volumes:
      - ./searxng:/etc/searxng:rw
      - searxng-data:/var/cache/searxng:rw
    environment:
      - SEARXNG_BASE_URL=https://searxng.mydomain.com
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
        max-file: "1"

volumes:
  valkey-data2:
  searxng-data:
  ts-searxng:
    driver: local

Just a few more things to note.

One, you obviously need to pay for and have Mullvad exit nodes enabled in Tailscale.

Two, after you add the Tailscale sidecar, your container will be a new "machine" on your Tailnet, and you'll have to add that machine to your list of devices that have Mullvad access in your Tailscale settings.

And three, here's how you can find the IP addresses of all your Mullvad exit nodes in Tailscale. Assuming you have a shell open in your Tailscale container, just run tailscale exit-node list. That's what you'll put in TS_EXTRA_ARGS=--exit-node=IP_OF_EXIT_NODE.

Confirming the exit node is actually being used

You can confirm your container is using the exit node in a couple ways. For containers that can run curl, you can use sudo docker exec CONTAINER_NAME apk add curl && curl https://ifconfig.co/json.

SearXNG doesn't let you install or use curl, so I typically use wget -qO- https://ifconfig.me/ip | cat to get the IP the container is using.