Nathaniel
01/01/2026, 7:30 PMMarvin
01/01/2026, 7:30 PMMarvin
01/01/2026, 7:32 PM--host 0.0.0.0 or PREFECT_SERVER_API_HOST=0.0.0.0.
services:
prefect-server:
image: prefecthq/prefect:3-latest
command: prefect server start --host 0.0.0.0 --port 4200 --no-services
environment:
# Ensures the API binds to all interfaces for LAN access
PREFECT_SERVER_API_HOST: 0.0.0.0
ports:
- "4200:4200"
restart: unless-stopped
Note: In Prefect 3.x, the UI and API are served on the same port (default 4200). The dashboard is at / and the health endpoint is /api/health.
Quick diagnostics
- From the Docker host:
docker ps
# confirm "0.0.0.0:4200->4200/tcp" or similar appears for the Prefect container
docker logs <prefect-server-container> --tail=100
# If you have curl on the host:
curl -sS <http://localhost:4200/api/health>
- From your dev device on the same network:
curl -sS <http://YOUR_SERVER_IP:4200/api/health>
Expect: {"status":"healthy"}
If you’re running the full stack (Postgres/Redis/services), a common compose snippet looks like:
services:
postgres:
image: postgres:14
environment:
POSTGRES_USER: prefect
POSTGRES_PASSWORD: prefect
POSTGRES_DB: prefect
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7
volumes:
- redis_data:/data
prefect-server:
image: prefecthq/prefect:3-latest
depends_on:
- postgres
- redis
environment:
PREFECT_API_DATABASE_CONNECTION_URL: <postgresql+asyncpg://prefect:prefect@postgres:5432/prefect>
PREFECT_SERVER_API_HOST: 0.0.0.0
PREFECT_MESSAGING_BROKER: prefect_redis.messaging
PREFECT_MESSAGING_CACHE: prefect_redis.messaging
PREFECT_REDIS_MESSAGING_HOST: redis
PREFECT_REDIS_MESSAGING_PORT: 6379
command: prefect server start --no-services
ports:
- "4200:4200"
prefect-services:
image: prefecthq/prefect:3-latest
depends_on:
- prefect-server
environment:
PREFECT_API_DATABASE_CONNECTION_URL: <postgresql+asyncpg://prefect:prefect@postgres:5432/prefect>
PREFECT_MESSAGING_BROKER: prefect_redis.messaging
PREFECT_MESSAGING_CACHE: prefect_redis.messaging
PREFECT_REDIS_MESSAGING_HOST: redis
PREFECT_REDIS_MESSAGING_PORT: 6379
command: prefect server services start
volumes:
postgres_data:
redis_data:
Other things to check
- Firewalls: Ensure the host allows inbound TCP 4200 from your LAN.
- Reverse proxy/TLS: If you’re fronting Prefect with a proxy and using a different hostname/port, you may need to set PREFECT_SERVER_UI_API_URL so the UI can reach the API.
- Don’t set PREFECT_API_URL in the server container; that’s for workers/clients.
Docs for reference
- Self-hosted via Docker Compose
- Server in Docker
- Settings reference (see PREFECT_SERVER_API_HOST, PREFECT_SERVER_UI_API_URL)
If you can, please share:
- Your docker-compose.yml service(s) for Prefect
- The URL you’re trying from your dev device
- Output of:
docker ps
docker logs <prefect-server-container> --tail=100
curl -sS <http://YOUR_SERVER_IP:4200/api/health> # from the dev device
I’ll pinpoint what’s blocking access.Nathaniel
01/01/2026, 7:36 PMMarvin
01/01/2026, 7:36 PMMarvin
01/01/2026, 7:37 PMtrue (not JSON). So what you saw is expected.
A couple of handy probes:
- Liveness: http://YOUR_SERVER_IP:4200/api/health → returns true
- Readiness (checks DB): http://YOUR_SERVER_IP:4200/api/ready → returns {"message":"OK"} on success or 503 with an error if the DB isn’t reachable
If curl to /api/health from your dev device works, but you still can’t load the dashboard in a browser, likely culprits are:
- The UI/API isn’t bound externally: ensure your compose sets PREFECT_SERVER_API_HOST=0.0.0.0 or --host 0.0.0.0, and that you’ve published 4200:4200.
- Browser reaching a different host/port than you’re curling (e.g., proxy or HTTPS mismatch).
- Firewall blocking port 4200.
If you can share:
- The Prefect service section of your docker-compose.yml
- The exact URL you’re hitting in the browser
- Output of:
docker ps
docker logs <prefect-server-container> --tail=100
curl -sS <http://YOUR_SERVER_IP:4200/api/ready>
I’ll pinpoint the next step.Nathaniel
01/01/2026, 7:43 PMMarvin
01/01/2026, 7:43 PMMarvin
01/01/2026, 7:44 PM--no-ui, and make sure PREFECT_UI_ENABLED=true (or PREFECT_SERVER_UI_ENABLED=true) isn’t overridden to false.
- If you’re using a proxy or a different hostname/port, set:
PREFECT_SERVER_UI_API_URL=<http://my_ip:4200/api>
This gives the UI an explicit API endpoint your browser can reach.
Quick compose snippet (key bits):
services:
prefect-server:
image: prefecthq/prefect:3-latest
command: prefect server start --host 0.0.0.0 --port 4200 --no-services
environment:
PREFECT_SERVER_API_HOST: 0.0.0.0
# Optional but helpful if proxying/alternate hostnames:
# PREFECT_SERVER_UI_API_URL: <http://my_ip:4200/api>
ports:
- "4200:4200"
Docs for reference:
- Server in Docker
- Settings reference (see PREFECT_UI_ENABLED, PREFECT_SERVER_UI_API_URL)
Can you try http://my_ip:4200/ directly and let me know what you see? If it’s still not loading, please share any console/network errors from the browser or the last 100 lines of the Prefect server logs:
docker logs <prefect-server-container> --tail=100Nathaniel
01/01/2026, 7:48 PMMarvin
01/01/2026, 7:48 PMMarvin
01/01/2026, 7:50 PMenvironment:
# UI -> API URL your browser can reach
PREFECT_SERVER_UI_API_URL: <http://my_ip:4200/api>
# CORS: allow your UI origin(s)
PREFECT_SERVER_API_CORS_ALLOWED_ORIGINS: "<http://my_ip:4200>"
PREFECT_SERVER_API_CORS_ALLOWED_METHODS: "*"
PREFECT_SERVER_API_CORS_ALLOWED_HEADERS: "*"
Then restart the container.
Notes
- Origins must match exactly: scheme, host, and port. For example, http://my_ip:4200 is different from http://localhost:4200 or https://my_ip:4200.
- If you’re using a proxy/hostname, change both values to match it, e.g.:
PREFECT_SERVER_UI_API_URL: <https://prefect.example.com/api>
PREFECT_SERVER_API_CORS_ALLOWED_ORIGINS: "<https://prefect.example.com>"
- Defaults are permissive (*), but some proxies strip headers or the UI may infer an API URL that’s cross-origin. Setting PREFECT_SERVER_UI_API_URL removes ambiguity.
Sanity checks
- After setting these, open your browser’s DevTools -> Network:
- Requests to /api/* should now include Access-Control-Allow-Origin in responses.
- Preflight OPTIONS requests should return 200 with the CORS headers.
- Verify endpoints directly from the browser:
- http://my_ip:4200/api/health → true
- http://my_ip:4200/api/ready → {"message":"OK"}
If you still see CORS errors, please share:
- The exact URL in your browser’s address bar
- The Request URL and Response Headers for a failed Network request
- Your Prefect service’s environment section from docker-compose.yml
I’ll tailor the exact values you need.