<@ULVA73B9P> tell me the best practice to connect ...
# ask-marvin
m
@Marvin tell me the best practice to connect react frontend container with prefect backend container
m
thought for 155.9 seconds
Best practice: don’t connect your React app directly to the Prefect API from the browser in production. Instead, use: React frontendyour backend/BFFPrefect API Why this is the safest/recommended pattern: - keeps Prefect credentials out of the browser - avoids CORS/auth headaches - lets you enforce your own authorization rules - gives you one place for logging, retries, rate limiting, and API shaping Recommended architecture
Copy code
text
React container  --->  app backend container  --->  Prefect server/container
(browser)              (Node/Python/FastAPI)        (Prefect API/UI)
If you’re self-hosting Prefect: - your backend should call Prefect over the Docker/Kubernetes internal network - your React app should call only your backend Docker example Use the container/service name, not
localhost
, for container-to-container calls.
Copy code
yaml
services:
  prefect:
    image: prefecthq/prefect:3-latest
    command: prefect server start --host 0.0.0.0
    ports:
      - "4200:4200"

  backend:
    build: ./backend
    environment:
      PREFECT_API_URL: <http://prefect:4200/api>
    depends_on:
      - prefect

  frontend:
    build: ./frontend
    environment:
      VITE_API_URL: <http://localhost:8000>
    depends_on:
      - backend
Key point: - inside
backend
, use
<http://prefect:4200/api>
- from the browser, use whatever public URL exposes your backend, e.g.
<http://localhost:8000>
Do not do this from React directly:
Copy code
text
<http://prefect:4200/api>
That hostname only resolves inside the Docker network, not in the user’s browser. When direct browser → Prefect is acceptable Only for: - local prototypes - internal tools with very controlled access - cases where you fully understand the CORS/auth tradeoffs If you do it, you’ll need CORS configured on Prefect Server, but I would still avoid exposing Prefect credentials in frontend code. Security guidance - never put
PREFECT_API_KEY
or server auth credentials in React env vars - keep Prefect API internal if possible - expose only your backend publicly - have your backend enforce which flows/deployments users can trigger or view For Kubernetes Same idea: - React service talks to your backend service - backend service talks to Prefect service via cluster DNS, e.g.
<http://prefect-server:4200/api>
- only ingress your frontend/backend, not necessarily Prefect itself Typical backend responsibilities Your backend can: - list flows/deployments - create flow runs - read run state - stream filtered logs - map your app’s users/roles to allowed Prefect actions Good pattern for API endpoints Example backend endpoints: -
GET /api/prefect/deployments
-
POST /api/prefect/flow-runs
-
GET /api/prefect/flow-runs/:id
Then your backend translates those into Prefect API calls. If you’re using Prefect Cloud Same recommendation applies even more strongly: - React should not hold a long-lived Prefect Cloud API key - your backend should use the key server-side Useful docs: - Prefect docs: docs.prefect.io - Prefect server concepts: Prefect Server - Prefect API/client concepts: API reference If you want, I can give you a complete working example for either: 1. Docker Compose with React + FastAPI + Prefect, or 2. Kubernetes ingress/service setup
m
@Marvin Docker Compose with React + FastAPI + Prefect
m
thought for 117.0 seconds
Absolutely — here’s a working Docker Compose pattern for React + FastAPI + Prefect 3 that follows the recommended architecture: ReactFastAPIPrefect API I’ll keep it practical and minimal. --- ## Recommended project structure
Copy code
text
your-project/
  docker-compose.yml
  .env
  backend/
    Dockerfile
    requirements.txt
    app/
      main.py
  frontend/
    Dockerfile
    package.json
    src/
      App.jsx
--- ## 1)
docker-compose.yml
Copy code
yaml
services:
  prefect:
    image: prefecthq/prefect:3-latest
    command: prefect server start --host 0.0.0.0 --port 4200
    ports:
      - "4200:4200"
    environment:
      PREFECT_SERVER_API_HOST: 0.0.0.0
      PREFECT_SERVER_API_PORT: 4200
    healthcheck:
      test: ["CMD", "curl", "-f", "<http://localhost:4200/api/health>"]
      interval: 10s
      timeout: 5s
      retries: 12

  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      PREFECT_API_URL: <http://prefect:4200/api>
      FRONTEND_ORIGIN: <http://localhost:3000>
    depends_on:
      prefect:
        condition: service_healthy

  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    environment:
      VITE_API_BASE_URL: <http://localhost:8000>
    depends_on:
      - backend
Why this works -
backend
reaches Prefect at
<http://prefect:4200/api>
using the service name
prefect
- browser-based React reaches FastAPI at
<http://localhost:8000>
- browser never talks directly to Prefect --- ## 2) FastAPI backend ###
backend/requirements.txt
Copy code
text
fastapi
uvicorn[standard]
httpx
###
backend/Dockerfile
Copy code
dockerfile
FROM python:3.11-slim

WORKDIR /app

RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app /app/app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
###
backend/app/main.py
```python import os import httpx from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware PREFECT_API_URL = os.getenv("PREFECT_API_URL", "http://prefect:4200/api") FRONTEND_ORIGIN = os.getenv("FRONTEND_ORIGIN", "http://localhost:3000") app = FastAPI(title="FastAPI Prefect Proxy") app.add_middleware( CORSMiddleware, allow_origins=[FRONTEND_ORIGIN], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) client = httpx.AsyncClient(base_url=PREFECT_API_URL, timeout=30.0) @app.get("/health") async def health(): try: response = await client.get("/health") response.raise_for_status() return { "backend": "ok", "prefect": response.json(), "prefect_api_url": PREFECT_API_URL, } except Exception as exc: raise HTTPException(status_code=503, detail=str(exc)) @app.get("/api/flows") async def list_flows(): try: response = await client.post("/flows/filter", json={}) response.raise_for_status() return response.json() except httpx.HTTPError as exc: raise HTTPException(status_code=500, detail=str(exc)) @app.get("/api/deployments") async def list_deployments(): try: response = await client.post("/deployments/filter", json={}) response.raise_for_status() return response.json() except httpx.HTTPError as exc: raise HTTPException(status_code=500, detail=str(exc)) @app.post("/api/deployments/{deployment_id}/run") async def run_deployment(deployment_id: str, payload: dict | None = None): try: response = await client.post( f"/deployments/{deployment_id}/create_flow_run", json=payload or {}, ) response.raise_for_status() return response.json() except httpx.HTTPError as exc: raise HTTPException(status_code=500, detail=str(exc)) @app.get("/api/flow-runs") async def list_flow_runs(): try:
response = await client.post("/flow_runs/filter", json={}) response.raise_for_status() return response.json() except httpx.HTTPError as exc: raise HTTPException(status_code=500, detail=str(exc))
Copy code
A few notes:
- I’m using the HTTP API directly from FastAPI because it’s simple for a proxy layer
- This keeps your React app decoupled from Prefect internals
- You *can* also use the Python client via `prefect.client.orchestration.get_client`, but for a thin web proxy, plain `httpx` is often easier

---

## 3) React frontend

Below is a minimal Vite-style example.

### `frontend/Dockerfile`

```dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 3000

CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "3000"]
###
frontend/src/App.jsx
Copy code
jsx
import { useEffect, useState } from "react";

const API_BASE = import.meta.env.VITE_API_BASE_URL || "<http://localhost:8000>";

export default function App() {
  const [deployments, setDeployments] = useState([]);
  const [flowRuns, setFlowRuns] = useState([]);
  const [error, setError] = useState("");

  async function loadDeployments() {
    try {
      const res = await fetch(`${API_BASE}/api/deployments`);
      const data = await res.json();
      setDeployments(data);
    } catch (err) {
      setError(String(err));
    }
  }

  async function loadFlowRuns() {
    try {
      const res = await fetch(`${API_BASE}/api/flow-runs`);
      const data = await res.json();
      setFlowRuns(data);
    } catch (err) {
      setError(String(err));
    }
  }

  async function runDeployment(id) {
    try {
      const res = await fetch(`${API_BASE}/api/deployments/${id}/run`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          parameters: {},
        }),
      });
      const data = await res.json();
      alert(`Created flow run: ${data.id}`);
      loadFlowRuns();
    } catch (err) {
      setError(String(err));
    }
  }

  useEffect(() => {
    loadDeployments();
    loadFlowRuns();
  }, []);

  return (
    <div style={{ padding: 24, fontFamily: "sans-serif" }}>
      <h1>Prefect Dashboard</h1>

      {error && <p style={{ color: "red" }}>{error}</p>}

      <h2>Deployments</h2>
      <ul>
        {deployments.map((d) => (
          <li key={d.id}>
            <strong>{d.name}</strong>{" "}
            <button onClick={() => runDeployment(d.id)}>Run</button>
          </li>
        ))}
      </ul>

      <h2>Flow Runs</h2>
      <ul>
        {flowRuns.map((fr) => (
          <li key={fr.id}>
            {fr.name} - {fr.state?.type}
          </li>
        ))}
      </ul>
    </div>
  );
}
--- ## 4) Example frontend
package.json
Copy code
json
{
  "name": "prefect-react-ui",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "dev": "vite"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "vite": "^5.4.0"
  }
}
--- ## 5) Run it
Copy code
bash
docker compose up --build
Then open: - React app: http://localhost:3000 - FastAPI health: http://localhost:8000/health - Prefect UI/API: http://localhost:4200 --- ## 6) Important best practices ### A. Don’t use
localhost
between containers
Inside Docker: -
backend
prefect
must use
<http://prefect:4200/api>
- not
<http://localhost:4200/api>
localhost
from inside
backend
means “the backend container itself”. ### B. Keep Prefect behind your backend Your React app should call: -
<http://localhost:8000/api/>...
not: -
<http://localhost:4200/api/>...
### C. Add auth in FastAPI if this is real production This example is intentionally minimal. In production you should add: - user auth - authorization rules - rate limiting - request validation - filtered/approved Prefect operations only
### D. Prefer a real DB for serious self-hosted use For a quick demo,
prefect server start
is fine. For more durable setups, use Postgres with Prefect Server. --- ## 7) Optional: use the Prefect Python client instead of raw HTTP If you want your FastAPI backend to use the official client, the entry point is:
prefect.client.orchestration:get_client
I verified that signature exists. That said, for a browser-facing proxy,
httpx
is often simpler unless you need richer Prefect client behavior. Docs: - Prefect docs - API reference Also, I verified the server command:
Copy code
bash
prefect server start --host 0.0.0.0 --port 4200
--- ## 8) One likely gotcha Your React UI will only show deployments/runs if you actually have flows and deployments registered in Prefect. If you want, I can next give you a complete example flow + deployment that you can add to this stack so the React UI has something real to trigger.