# unmask with Docker Compose -- one container: the gateway. # # 1. docker compose -f docker-compose.example.yml up -d # 2. docker compose -f docker-compose.example.yml logs unmask | grep "setup token" # 3. open https://localhost/unmask/admin/ -> install wizard (paste the token). # The first start serves a self-signed certificate (your browser warns # once); Settings > Gateway is where the upstream, the hostnames and the # real certificate (Let's Encrypt, pasted, or mounted files) are set. # # The unmask image holds both halves of a host install: nginx (the official # image with the unmask module) terminates TLS -- that is where JA4 comes # from -- classifies, and either serves the challenge or proxies to the # upstream; the unmask daemon (admin UI, challenge verification, ban list, # stats) renders nginx's includes and answers on the loopback. One # entrypoint keeps both up: a daemon restart costs no outage (nginx lets # traffic through while it is away), an nginx exit restarts the container. # # The volumes are not optional: /etc/unmask carries the rendered includes and # the secrets; drop it and every restart mints new secrets, which logs every # visitor out and re-challenges them. # # Settings changed in the admin UI are rendered into /etc/unmask and nginx # reloads itself within a few seconds (after nginx -t; a render that does # not parse is logged and skipped). UNMASK_AUTORELOAD=0 disables that. services: unmask: image: unmask.sh/unmask:latest # mirrored at ghcr.io/unmask-sh/unmask # To build the image from a checkout of the repository instead of # pulling it, uncomment the next line and run `docker compose build`. # Left in, it makes `up` on a host without the Dockerfile (this file is # also shipped on its own) fail on the missing build context. # build: . container_name: unmask restart: unless-stopped environment: TZ: UTC # the container's clock, for its logs; set your own zone # Everything about the gateway lives in the admin UI, Settings > # Gateway: the upstream, the hostnames it answers for, and the # certificates. The variables below only seed that tab on the first # boot; leave them out and the tab starts with any hostname, a # self-signed certificate, and the upstream below. # # Where passing requests go (seed; change it in the tab later). Any # HTTP server works, nothing is installed on it: a container in this # compose (http://app:80), another host (http://10.0.0.5:8080), or a # server on this Docker host moved to 8080 (http://host.docker.internal:8080). UNMASK_UPSTREAM: http://app:80 # Seeds, all optional: # UNMASK_SERVER_NAME: example.com # the hostnames to answer for (space separated; "_" = any) # UNMASK_ACME_EMAIL: you@example.com # automatic HTTPS: nginx's own ACME module obtains # # and renews a Let's Encrypt certificate for the # # hostnames (:80 reachable from the internet) # UNMASK_ACME_DIRECTORY: https://acme-staging-v02.api.letsencrypt.org/directory # staging, while testing # UNMASK_TLS_CERT: /etc/unmask/tls/fullchain.pem # your own files (./tls, mounted below) # UNMASK_TLS_KEY: /etc/unmask/tls/privkey.pem # UNMASK_TLS_MODE: none # a load balancer in front terminates TLS and forwards on :80 # UNMASK_TRUSTED_PROXIES: "130.211.0.0/22 35.191.0.0/16" # that balancer's ranges, as a custom row of # # Settings > Network > trusted LB / CDN (presets there too) # Lets the container reach a server on the Docker host itself # (upstream http://host.docker.internal:8080 -- a server you moved to # port 8080, bound to an address the container can reach, not # 127.0.0.1). Harmless otherwise. extra_hosts: ["host.docker.internal:host-gateway"] volumes: - unmask-config:/etc/unmask # config.yml, secrets, the rendered includes - unmask-data:/var/lib/unmask # the database - unmask-acme:/var/cache/nginx/unmask-acme # ACME account + certificate; keep it # Your own certificate files, if any (Settings > Gateway > files), # read-only: the daemon reads a mounted certificate's names (SANs) and # never touches the key. - ./tls:/etc/unmask/tls:ro ports: - "80:80" - "443:443" # Stand-in upstream so the example answers on its own. Replace with your # application, or point the upstream (Settings > Gateway) at one running # elsewhere and drop this service. app: image: traefik/whoami container_name: unmask-example-app restart: unless-stopped volumes: unmask-config: unmask-data: unmask-acme: