Add make dev-lan: the app on a host port, API proxied on the same origin
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
# Convenience targets for development, testing, linting, and deployment.
|
# Convenience targets for development, testing, linting, and deployment.
|
||||||
# The workspace root delegates to these (see ../Makefile).
|
# The workspace root delegates to these (see ../Makefile).
|
||||||
|
|
||||||
.PHONY: dev-utils dev dev-local up down update install dev-backend dev-frontend \
|
.PHONY: dev-utils dev dev-local dev-lan up down update install dev-backend dev-frontend \
|
||||||
generate-client seed-example seed-demo seed-house seed-aircon seed-hosted-demo test test-backend test-frontend soak bench-startup lint lint-backend \
|
generate-client seed-example seed-demo seed-house seed-aircon seed-hosted-demo test test-backend test-frontend soak bench-startup lint lint-backend \
|
||||||
lint-frontend format-frontend umami build docs docs-serve clean help
|
lint-frontend format-frontend umami build docs docs-serve clean help
|
||||||
|
|
||||||
@@ -17,9 +17,14 @@ COMPOSE_PROD := $(COMPOSE) -f docker/compose.yml
|
|||||||
# Production also runs autoheal, which restarts the backend when its deep
|
# Production also runs autoheal, which restarts the backend when its deep
|
||||||
# health check fails. It is profile-gated because it mounts the Docker socket.
|
# health check fails. It is profile-gated because it mounts the Docker socket.
|
||||||
COMPOSE_PROD_RUN := $(COMPOSE_PROD) --profile autoheal
|
COMPOSE_PROD_RUN := $(COMPOSE_PROD) --profile autoheal
|
||||||
|
# Host port `make dev-lan` publishes the frontend on.
|
||||||
|
APP_PORT ?= 8080
|
||||||
COMPOSE_DEV := $(COMPOSE_PROD) -f docker/compose.dev.yml
|
COMPOSE_DEV := $(COMPOSE_PROD) -f docker/compose.dev.yml
|
||||||
# Integrated local stack: dev stack wired onto the shared `proxy` network.
|
# Integrated local stack: dev stack wired onto the shared `proxy` network.
|
||||||
COMPOSE_LOCAL := $(COMPOSE_DEV) -f docker/compose.local.yml
|
COMPOSE_LOCAL := $(COMPOSE_DEV) -f docker/compose.local.yml
|
||||||
|
# Same stack, with the frontend also published on APP_PORT and proxying the API
|
||||||
|
# there, for clients that cannot resolve app.$(DOMAIN).
|
||||||
|
COMPOSE_LAN := $(COMPOSE_LOCAL) -f docker/compose.lan.yml
|
||||||
|
|
||||||
help: ## Show available targets
|
help: ## Show available targets
|
||||||
@awk 'BEGIN{FS=":.*?## "} /^[a-zA-Z_-]+:.*?##/ {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
@awk 'BEGIN{FS=":.*?## "} /^[a-zA-Z_-]+:.*?##/ {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||||
@@ -36,6 +41,10 @@ dev-local: ## Start the integrated local stack (called by the root `make dev`)
|
|||||||
DOMAIN=$${DOMAIN:-localhost} ENVIRONMENT=$${ENVIRONMENT:-local} \
|
DOMAIN=$${DOMAIN:-localhost} ENVIRONMENT=$${ENVIRONMENT:-local} \
|
||||||
$(COMPOSE_LOCAL) up --build -d proxy backend frontend mailcatcher
|
$(COMPOSE_LOCAL) up --build -d proxy backend frontend mailcatcher
|
||||||
|
|
||||||
|
dev-lan: ## Same, plus the app on http://<host-ip>:$(APP_PORT) (no DNS needed)
|
||||||
|
DOMAIN=$${DOMAIN:-localhost} ENVIRONMENT=$${ENVIRONMENT:-local} APP_PORT=$(APP_PORT) \
|
||||||
|
$(COMPOSE_LAN) up --build -d proxy backend frontend mailcatcher
|
||||||
|
|
||||||
up: ## Start the production stack
|
up: ## Start the production stack
|
||||||
$(COMPOSE_PROD_RUN) up --build -d
|
$(COMPOSE_PROD_RUN) up --build -d
|
||||||
|
|
||||||
|
|||||||
@@ -289,3 +289,11 @@ Open on purpose. Each names what should bring it back.
|
|||||||
container `fluksio-app-proxy-1`. With `REVERSE_PROXY=external` and no host proxy,
|
container `fluksio-app-proxy-1`. With `REVERSE_PROXY=external` and no host proxy,
|
||||||
the whole stack then has nothing bound to :80 and `*.localhost` stops resolving.
|
the whole stack then has nothing bound to :80 and `*.localhost` stops resolving.
|
||||||
Either drop `--remove-orphans` or make `update` refuse when `ENVIRONMENT=local`.
|
Either drop `--remove-orphans` or make `update` refuse when `ENVIRONMENT=local`.
|
||||||
|
|
||||||
|
- CHORE: `frontend/vite.config.js` and `frontend/vite.config.d.ts` are committed build
|
||||||
|
output of `vite.config.ts` and fail `biome check` as tracked (semicolons, 4-space
|
||||||
|
indent). The pre-commit hook therefore fails for anyone who stages any other file
|
||||||
|
under `frontend/`. Either gitignore the two or run the formatter over them once.
|
||||||
|
- CHORE: the pre-commit biome hook selects files by the `frontend/` prefix rather than by
|
||||||
|
extension, so a non-JS file placed there is handed to biome and blows up with an
|
||||||
|
internal error on a doubled path (`frontend/frontend/...`).
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Direct LAN access — layered on top of compose.local.yml by `make dev-lan`.
|
||||||
|
#
|
||||||
|
# For reaching the app from a machine that cannot be given a DNS record or a
|
||||||
|
# hosts entry. Traefik routes on the Host header and a bare IP matches no
|
||||||
|
# router, so the frontend is published on a host port instead and proxies the
|
||||||
|
# API on that same port: http://<host-ip>:${APP_PORT} is the whole app.
|
||||||
|
services:
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
ports: !override
|
||||||
|
- "${APP_PORT:-8080}:80"
|
||||||
|
volumes:
|
||||||
|
- ./nginx-api-proxy.conf:/etc/nginx/extra-conf.d/backend-not-found.conf:ro
|
||||||
|
build:
|
||||||
|
args:
|
||||||
|
# Empty on purpose: the client then addresses the API as a path on
|
||||||
|
# whichever origin served the page, so one build works through
|
||||||
|
# app.${DOMAIN}, the published port, and an ssh tunnel alike.
|
||||||
|
- VITE_API_URL=
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Serves the API from the same origin as the SPA, replacing the 404 stub the
|
||||||
|
# image ships. Traefik routes on the Host header, which a bare IP never
|
||||||
|
# matches, so this is what makes http://<host-ip>:<port> a complete app: the
|
||||||
|
# client asks for /api/v1/... on whatever origin served it, and never needs a
|
||||||
|
# resolvable api.${DOMAIN} or a CORS allowance.
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
# Docker's embedded DNS, re-resolved: nginx otherwise pins the backend's
|
||||||
|
# address at startup and 502s for good once that container is recreated.
|
||||||
|
resolver 127.0.0.11 valid=10s ipv6=off;
|
||||||
|
set $backend_upstream http://backend:8000;
|
||||||
|
proxy_pass $backend_upstream$request_uri;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
# The flow websocket (/api/v1/flows/ws) upgrades through here; passing the
|
||||||
|
# client's own Connection header covers both it and ordinary requests.
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $http_connection;
|
||||||
|
# A live socket outlives the 60s default by design.
|
||||||
|
proxy_read_timeout 1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Unchanged from the stub: the API docs stay off the frontend's origin.
|
||||||
|
location /docs {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
location /redoc {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user