Moat is a lightweight, FastAPI-based security/SSO gateway that provides authentication and reverse proxying capabilities for your web services. It can manage service discovery through Docker labels or static configuration.
Moat_Demo.mp4
- Cookie-based authentication for downstream services.
- Reverse proxy for multiple backend applications.
- Dynamic service registration using Docker container labels.
- Static service registration via configuration file.
- Web UI for managing Moat's configuration.
- CLI for user management and configuration tasks.
- Hot-reloading of service configuration (static and Docker-based).
- Python 3.8+
- Docker (optional, if using Docker monitor feature)
- An ASGI server like Uvicorn (included in
requirements.txt)
-
Clone the repository (or download the source):
git clone https://github.com/9-5/moat cd moat -
Create a virtual environment (recommended):
python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
-
Install dependencies:
pip install -r requirements.txt
Moat uses a config.yml file for its settings.
-
Initialize Configuration: If
config.ymldoesn't exist in the root directory where you'll run Moat, create one using the CLI:python -m moat.main init-config
This will create a
config.ymlfile with default values. -
Edit
config.yml: Openconfig.ymland critically review and update the following:secret_key: MUST be changed to a strong, unique random string. You can generate one withopenssl rand -hex 32. This key is used for signing access tokens.moat_base_url: Set this to the public URL where Moat's own UI and authentication endpoints will be accessed (e.g.,https://auth.yourdomain.com). This is crucial if Moat itself is behind another reverse proxy or accessed via a tunnel.cookie_domain: Configure this for Single Sign-On (SSO) across your services.- For subdomains (e.g.,
app1.yourdomain.com,app2.yourdomain.com), use a leading dot:.yourdomain.com. - If all apps are on the same specific hostname as Moat, or if you are not using subdomains for apps, you might use the specific hostname or leave it as
null(Moat will then use the hostname from the request to its own login page, which might be less predictable in complex setups).
- For subdomains (e.g.,
database_url: Defines the path to the SQLite database. Default is./moat.db.docker_monitor_enabled: Set totrueorfalseto enable/disable Docker event monitoring.moat_label_prefix: The prefix for Docker labels Moat will look for (e.g.,moat.enable).static_services: Define services that are not managed by Docker. See examples in the generated file.
-
Ensure
config.ymlis configured. -
Add an initial admin user: You need at least one user to log in to Moat's admin UI and for services protected by Moat.
python -m moat.main add-user
Follow the prompts to set a username and password.
-
Run the server:
python -m moat.main run
By default, it runs on
0.0.0.0:8000. You can override this with--hostand--portoptions, or by changinglisten_hostandlisten_portinconfig.yml.For development of Moat itself, you can enable uvicorn's auto-reload:
python -m moat.main run --reload
-
Static Services: Add entries to the
static_serviceslist inconfig.yml:static_services: - hostname: "myapp.yourdomain.com" target_url: "http://localhost:3000" # URL of your backend service - hostname: "another-app.yourdomain.com" target_url: "http://192.168.1.50:8080"
Or use the CLI command (this modifies
config.yml):python -m moat.main config:add-static
-
Docker Dynamic Services: If
docker_monitor_enabled: true, Moat will automatically detect and proxy services from running Docker containers that have specific labels. The default prefix ismoat. Required labels on the container:moat.enable="true"moat.hostname="service.yourdomain.com"(The public hostname Moat will listen on)moat.port="80"(The internal port the service listens on inside the container) Optional label:moat.scheme="http"(orhttps, default ishttp)
Example Docker run command:
docker run -d \ --label moat.enable="true" \ --label moat.hostname="myservice.yourdomain.com" \ --label moat.port="3000" \ my-service-image
Moat will then proxy requests for
myservice.yourdomain.comto this container on port3000.
Use the cloudflared guide.
Once Moat is running and services are configured:
- Access a protected service via its public hostname (e.g.,
http://myapp.yourdomain.com). - If not authenticated, Moat will redirect you to its login page (hosted at
moat_base_url+/moat/auth/login). - After successful login, you'll be redirected back to the originally requested service.
- The authentication cookie will be set for the
cookie_domainspecified inconfig.yml, allowing SSO.
If you are authenticated and access Moat directly via its moat_base_url (e.g., https://auth.yourdomain.com/), you will be redirected to the admin configuration page (/moat/admin/config). Here you can view and edit the config.yml content directly. Changes to static_services and Docker monitor settings are hot-reloaded.
Moat provides a few CLI commands:
python -m moat.main run [--host <host>] [--port <port>] [--reload]: Runs the server.python -m moat.main init-config [--force]: Creates a defaultconfig.yml.python -m moat.main add-user: Adds a new user to the database.python -m moat.main config:add-static: Adds a static service entry toconfig.yml.python -m moat.main docker:bind <container_name_or_id> --public-hostname <hostname>: Adds a running Docker container as a static service toconfig.yml(useful if not using Docker label discovery or for specific overrides).
- "Secret key not configured" / "Moat configuration file not found": Ensure
config.ymlexists in the working directory andsecret_keyis set. Runmoat init-config. - Redirect loops or incorrect login URL: Double-check
moat_base_urlinconfig.yml. It must be the public URL of Moat itself. - Cookies not working across subdomains: Verify
cookie_domainis set correctly (e.g.,.yourdomain.com). - Docker services not appearing:
- Ensure
docker_monitor_enabled: true. - Check Moat's logs for Docker connection errors.
- Verify container labels match
moat_label_prefixand includeenable,hostname, andport. - Ensure Moat has access to the Docker socket (
/var/run/docker.sock).
- Ensure


