commit 6accc7ca25409c7018febf3fe0841065c1a1fab4 Author: amadzarak Date: Sat Apr 25 17:27:47 2026 -0400 OPC # 0001: Extract gateway into standalone repo diff --git a/README.md b/README.md new file mode 100644 index 0000000..14da4ce --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# gateway + +Nginx reverse proxy + Dnsmasq DNS resolver for the ClarityStack local dev environment. + +## Structure + +``` +gateway/ +├── nginx/ +│ ├── nginx.conf +│ ├── conf.d/ # per-service virtual host configs +│ ├── clarity.test.crt +│ └── clarity.test.key +├── dnsmasq/ +│ └── dnsmasq.conf # resolves *.clarity.test → 127.0.0.1 +└── docker-compose.yml +``` + +## Usage + +```bash +docker compose up -d +``` + +Requires the external Docker network `clarity-net` to exist: + +```bash +docker network create clarity-net +``` + +## OPC #0039 + +Extracted from the original monorepo `infra/` as a standalone composable unit. diff --git a/dnsmasq/dnsmasq.conf b/dnsmasq/dnsmasq.conf new file mode 100644 index 0000000..0a42dfd --- /dev/null +++ b/dnsmasq/dnsmasq.conf @@ -0,0 +1,17 @@ +# Resolve all *.clarity.local subdomains to the loopback address. +# nginx (bound to port 80 on the host) then routes by subdomain to the correct tenant container. +address=/.clarity.test/127.0.0.1 + +# Don't read /etc/resolv.conf or /etc/hosts from the container — we are the resolver +no-resolv +no-hosts + +# Forward everything that isn't clarity.local to Cloudflare DNS +server=1.1.1.1 +server=8.8.8.8 + +# Listen on all interfaces inside the container +listen-address=0.0.0.0 + +# Log queries — useful during initial setup, can be removed later +log-queries diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..db85434 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +services: + nginx: + image: nginx:alpine + container_name: clarity-nginx + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/clarity.test.crt:/etc/nginx/certs/clarity.test.crt:ro + - ./nginx/clarity.test.key:/etc/nginx/certs/clarity.test.key:ro + networks: + - clarity-net + + dnsmasq: + image: dockurr/dnsmasq + container_name: clarity-dnsmasq + restart: unless-stopped + ports: + - "53:53/udp" + - "53:53/tcp" + volumes: + - ./dnsmasq/dnsmasq.conf:/etc/dnsmasq.conf:ro + cap_add: + - NET_ADMIN + networks: + - clarity-net + +networks: + clarity-net: + external: true + name: clarity-net diff --git a/nginx/conf.d/fdev-app-clarity-01000000.conf b/nginx/conf.d/fdev-app-clarity-01000000.conf new file mode 100644 index 0000000..1988f9f --- /dev/null +++ b/nginx/conf.d/fdev-app-clarity-01000000.conf @@ -0,0 +1,19 @@ +# Auto-generated by ControlPlane.Worker — do not edit manually. +# Tenant: fdev-app-clarity-01000000 +server { + listen 443 ssl; + server_name fdev-app-clarity-01000000.clarity.test; + + ssl_certificate /etc/nginx/certs/clarity.test.crt; + ssl_certificate_key /etc/nginx/certs/clarity.test.key; + + location / { + # Docker DNS resolves the container name on the managed network + set $upstream http://fdev-app-clarity-01000000:8080; + proxy_pass $upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/nginx/conf.d/fdev-app-clarity-02000000.conf b/nginx/conf.d/fdev-app-clarity-02000000.conf new file mode 100644 index 0000000..75c16d4 --- /dev/null +++ b/nginx/conf.d/fdev-app-clarity-02000000.conf @@ -0,0 +1,19 @@ +# Auto-generated by ControlPlane.Worker — do not edit manually. +# Tenant: fdev-app-clarity-02000000 +server { + listen 443 ssl; + server_name fdev-app-clarity-02000000.clarity.test; + + ssl_certificate /etc/nginx/certs/clarity.test.crt; + ssl_certificate_key /etc/nginx/certs/clarity.test.key; + + location / { + # Docker DNS resolves the container name on the managed network + set $upstream http://fdev-app-clarity-02000000:8080; + proxy_pass $upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/nginx/conf.d/fdev-app-clarity-03000000.conf b/nginx/conf.d/fdev-app-clarity-03000000.conf new file mode 100644 index 0000000..f92ebd6 --- /dev/null +++ b/nginx/conf.d/fdev-app-clarity-03000000.conf @@ -0,0 +1,19 @@ +# Auto-generated by ControlPlane.Worker — do not edit manually. +# Tenant: fdev-app-clarity-03000000 +server { + listen 443 ssl; + server_name fdev-app-clarity-03000000.clarity.test; + + ssl_certificate /etc/nginx/certs/clarity.test.crt; + ssl_certificate_key /etc/nginx/certs/clarity.test.key; + + location / { + # Docker DNS resolves the container name on the managed network + set $upstream http://fdev-app-clarity-03000000:8080; + proxy_pass $upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/nginx/conf.d/fdev-app-clarity-04000000.conf b/nginx/conf.d/fdev-app-clarity-04000000.conf new file mode 100644 index 0000000..06ca568 --- /dev/null +++ b/nginx/conf.d/fdev-app-clarity-04000000.conf @@ -0,0 +1,19 @@ +# Auto-generated by ControlPlane.Worker — do not edit manually. +# Tenant: fdev-app-clarity-04000000 +server { + listen 443 ssl; + server_name fdev-app-clarity-04000000.clarity.test; + + ssl_certificate /etc/nginx/certs/clarity.test.crt; + ssl_certificate_key /etc/nginx/certs/clarity.test.key; + + location / { + # Docker DNS resolves the container name on the managed network + set $upstream http://fdev-app-clarity-04000000:8080; + proxy_pass $upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/nginx/conf.d/gitea.conf b/nginx/conf.d/gitea.conf new file mode 100644 index 0000000..dbcc969 --- /dev/null +++ b/nginx/conf.d/gitea.conf @@ -0,0 +1,21 @@ +server { + listen 443 ssl; + server_name opc.clarity.test; + + ssl_certificate /etc/nginx/certs/clarity.test.crt; + ssl_certificate_key /etc/nginx/certs/clarity.test.key; + + # Git over HTTP needs larger body and longer timeouts + client_max_body_size 100m; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + + location / { + set $upstream http://host.docker.internal:3000; + proxy_pass $upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/nginx/conf.d/keycloak.conf b/nginx/conf.d/keycloak.conf new file mode 100644 index 0000000..aad2671 --- /dev/null +++ b/nginx/conf.d/keycloak.conf @@ -0,0 +1,16 @@ +server { + listen 443 ssl; + server_name keycloak.clarity.test; + + ssl_certificate /etc/nginx/certs/clarity.test.crt; + ssl_certificate_key /etc/nginx/certs/clarity.test.key; + + location / { + set $upstream http://keycloak:8080; + proxy_pass $upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..5dab5af --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,27 @@ +events { + worker_connections 1024; +} + +http { + # Use Docker's embedded DNS resolver so container names resolve dynamically. + # This is critical — without it nginx resolves upstream names at startup only + # and won't pick up newly provisioned tenant containers. + resolver 127.0.0.11 valid=5s ipv6=off; + + # Shared log format + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + # Redirect all HTTP → HTTPS + server { + listen 80 default_server; + return 301 https://$host$request_uri; + } + + # Pick up per-tenant server blocks dropped by the provisioning worker + include /etc/nginx/conf.d/*.conf; +}