Docker Deployment¶
Website Defender provides a multi-stage Dockerfile for building a minimal container image with all frontend assets and the Go binary.
Quick Start¶
Using Docker Compose (Recommended)¶
git clone https://github.com/Flmelody/open-website-defender.git
cd open-website-defender
# Copy and edit environment variables
cp .env.example .env
vim .env
# Edit runtime config as needed
vim config/config.yaml
# Build and start
docker compose up -d
The service will be available at http://localhost:9999.
Docker Compose automatically reads the .env file in the project root and passes the variables as build arguments to the Dockerfile. This ensures all configuration stays in one place.
Using Docker Directly¶
# Build the image (uses Dockerfile ARG defaults)
docker build -t defender .
# Or with custom build arguments
docker build \
--build-arg ROOT_PATH=/api \
-t defender .
# Run the container (works without any volume mounts)
docker run -d \
--name defender \
-p 9999:9999 \
defender
Build Arguments¶
The Dockerfile accepts build arguments to customize the frontend at build time. These values are baked into the frontend assets via Vite and into the Go binary via ldflags.
| Argument | Default | Description |
|---|---|---|
ROOT_PATH |
/wall |
API route prefix |
ADMIN_PATH |
/admin |
Admin dashboard path |
GUARD_PATH |
/guard |
Guard/challenge page path |
BACKEND_HOST is Now Runtime Configuration
BACKEND_HOST is no longer a build argument. It is configured at runtime via wall.backend-host in config.yaml or the BACKEND_HOST environment variable. Changing it does not require rebuilding the image.
Configuration Flow¶
Build arguments (ROOT_PATH, ADMIN_PATH, GUARD_PATH) share the same variable names as the .env file used by scripts/build.sh:
.env ──▶ docker-compose.yml (${VAR:-default}) ──▶ Dockerfile ARG
▼
scripts/build.sh ◀── .env Vite env + Go ldflags
config.yaml (wall.backend-host) ──▶ runtime config (no rebuild needed)
docker compose build: reads.envautomatically, passes values as build args, overriding Dockerfile ARG defaults.docker build(standalone): uses Dockerfile ARG defaults directly. Pass--build-argto override.BACKEND_HOST: resolved at runtime fromconfig.yaml(wall.backend-host) or theBACKEND_HOSTenvironment variable. Not part of the build.
Port Configuration¶
The application port can be configured at runtime via the PORT environment variable or server.port in config.yaml. In Docker Compose, PORT from .env drives both the port mapping and the application listening port:
To change the port, set PORT in .env:
Then restart:
Alternatively, set the port in config.yaml:
No rebuild is needed when changing the port. If using a non-standard port and your frontend is accessed from a different origin, set wall.backend-host in config.yaml to match.
Volumes¶
The container works out of the box without any volume mounts — default configuration and an SQLite database are embedded in the image. Volume mounts are optional and only needed when you want to persist data or override the default configuration.
| Path | Purpose | Required |
|---|---|---|
/app/data |
SQLite database (app.db) and other persistent data |
No (recommended for production) |
/app/config |
Runtime configuration (config.yaml) |
No (image includes defaults) |
# Minimal: no mounts, uses built-in defaults
docker run -d -p 9999:9999 defender
# Production: mount data for persistence and config for customization
docker run -d \
-p 9999:9999 \
-v $(pwd)/data:/app/data \
-v $(pwd)/config:/app/config \
defender
Data Persistence
Without mounting /app/data, the SQLite database lives inside the container and will be lost when the container is removed. For production use, always mount this path.
Configuration¶
The image ships with a default config/config.yaml. To customize it, mount your own config file:
The configuration file format is the same as for bare-metal deployments — see Configuration.
Using PostgreSQL¶
For production deployments, you can use PostgreSQL instead of SQLite.
- Uncomment the
postgresservice indocker-compose.yml - Update
config/config.yaml:
database:
driver: postgres
host: postgres
port: 5432
name: open_website_defender
user: postgres
password: changeme
- Start both services:
Docker Networking and Client IP Detection¶
IP-Based Features Require a Reverse Proxy or Host Network
When using Docker's default bridge network with port mapping (-p 9999:9999), all requests appear to come from the Docker gateway IP (e.g. 172.19.0.1) instead of the real client IP. This is because Docker port mapping is Layer 4 NAT — it does not set any HTTP headers.
This affects all IP-based features:
| Feature | Impact |
|---|---|
| IP Blacklist | Blocking an external IP has no effect |
| IP Whitelist | Allowing an external IP has no effect |
| Rate Limiting | All users share one IP's quota, causing false triggers |
| Access Logs | All logs show the gateway IP, not the real client |
| Geo-IP Blocking | Cannot determine real client location |
Blacklisting the gateway IP (e.g. 172.19.0.1) will block all requests entirely.
Solution 1: Nginx Reverse Proxy (Recommended)¶
Place Nginx in front of the container. Nginx sets the X-Forwarded-For header with the real client IP:
Then add the Nginx/Docker gateway IP to trustedProxies in config.yaml:
See Nginx Setup for the complete configuration.
Solution 2: Host Network Mode¶
Use network_mode: host so the container shares the host's network stack and sees real client IPs directly:
No trustedProxies configuration is needed in this mode. Note that ports mapping is ignored with host networking — the application binds directly to the host port.
Production Tips¶
Production Checklist
- Set a stable
security.jwt-secretinconfig.yaml - Change the default credentials (
defender/defender) - Use PostgreSQL or MySQL for better concurrency
- Configure
trustedProxiesto include your reverse proxy IPs - Set explicit
security.cors.allowed-origins - Use a named Docker volume or bind mount for
/app/data - Set
wall.backend-hostinconfig.yamlfor cross-origin deployments
Running Behind Nginx¶
When running the Docker container behind Nginx, add the Docker network gateway or Nginx host IP to trustedProxies:
See Nginx Setup for the full reverse proxy configuration.
Health Checks¶
Add a health check to your Docker Compose configuration:
services:
defender:
# ...
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:9999/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s