Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions docker-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,28 @@
This directory contains several examples of how to deploy Reflex apps using docker.

In all cases, ensure that your `requirements.txt` file is up to date and
includes the `reflex` package.
includes the `reflex` package. Commit the `reflex.lock/` directory that
`reflex init` creates so the frontend dependencies installed in the image match
the ones you developed against.

## `simple-two-port`
## `production`

The most basic production deployment exposes two HTTP ports and relies on an
existing load balancer to forward the traffic appropriately.

## `simple-one-port`

This deployment exports the frontend statically and serves it via a single HTTP
port using Caddy. This is useful for platforms that only support a single port
or where running a node server in the container is undesirable.
Start here. This single-container deployment exports the frontend statically
and serves it via a single HTTP port using Caddy, with a local Redis for state.
The backend starts instantly because the frontend is built into the image, and
a multi-stage build keeps bun, `node_modules`, and other build tooling out of
the final image. It works anywhere a container with one exposed port can run,
including platforms such as Render or Heroku.

## `production-compose`

This deployment is intended for use with a standalone VPS that is only hosting a
single Reflex app. It provides the entire stack in a single `compose.yaml`
including a webserver, one or more backend instances, redis, and a postgres
database.
single Reflex app. `compose.yaml` provides a webserver with automatic TLS in
front of the app, which stores its data in SQLite. Adding the
`compose.prod.yaml` override swaps in postgres and redis, which also lets the
backend run multiple workers.

## `production-app-platform`
## `app-platform-backend`

This example deployment is intended for use with App hosting platforms, like
Azure, AWS, or Google Cloud Run. It is the backend of the deployment, which
Expand Down
9 changes: 9 additions & 0 deletions docker-example/app-platform-backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.web
.git
.venv
__pycache__
*.py[cod]
.states
*.db
uploaded_files
Dockerfile
28 changes: 28 additions & 0 deletions docker-example/app-platform-backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# check=skip=JSONArgsRecommended
# Backend-only image for container hosting services. The frontend is exported
# separately and served from a static host; see README.md.
#
# Note: many container hosting platforms require amd64 images, so when building on an M1 Mac
# for example, pass `docker build --platform=linux/amd64 ...`
FROM python:3.13-slim

ENV UV_COMPILE_BYTECODE=1 UV_NO_CACHE=1 PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1

WORKDIR /app
# The app user needs to own /app itself so reflex can create .states there.
RUN adduser --disabled-password --home /app reflex && chown reflex /app

# Install python requirements first so app edits do not reinstall them. uv is
# mounted only for this step so it does not end up in the image.
COPY requirements.txt .
RUN --mount=from=ghcr.io/astral-sh/uv:0.12,source=/uv,target=/bin/uv \
uv venv && uv pip install -r requirements.txt

# Copy local context to `/app` inside container (see .dockerignore)
COPY --chown=reflex . .
USER reflex
RUN mkdir -p data uploaded_files

# Apply migrations before starting the backend; a failed migration stops the container.
CMD if [ -d alembic ]; then reflex db migrate; fi && \
exec reflex run --env prod --backend-only --backend-port ${PORT:-8000}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# production-app-platform
# app-platform-backend

This example deployment is intended for use with App hosting platforms, like
Azure, AWS, or Google Cloud Run.
Expand All @@ -8,7 +8,9 @@ Azure, AWS, or Google Cloud Run.
The production deployment consists of a few pieces:

- Backend container - built by `Dockerfile` Runs the Reflex backend
service on port 8000 and is scalable to multiple instances.
service on port 8000 (or `$PORT`) and is scalable to multiple instances.
The image contains only the python environment and app source: no bun,
`node_modules`, or frontend build.
- Redis container - A single instance the standard `redis` docker image should
share private networking with the backend
- Static frontend - HTML/CSS/JS files that are hosted via a CDN or static file
Expand Down Expand Up @@ -36,19 +38,35 @@ The backend is built by the `Dockerfile` in this directory. When deploying the
backend, be sure to set REFLEX_REDIS_URL=redis://internal-redis-hostname to connect to
the redis service.

With redis available, each replica runs `2 * cpu_count + 1` worker processes.
Set `GRANIAN_WORKERS` to cap this on small instance sizes.

If the app uses postgres, add `psycopg[binary]` to `requirements.txt`; the
binary wheel bundles libpq, so no extra system packages are needed in the image.

### Ingress

Configure the load balancer for the app to forward traffic to port 8000 on the
backend service replicas. Most platforms will generate an ingress hostname
automatically. Make sure when you access the ingress endpoint on `/ping` that it
returns "pong", indicating that the backend is up an available.

The load balancer must support websockets (pass the `Upgrade` header) for the
event connection to work.

### Frontend

The frontend should be hosted on a static file server or CDN.

**Important**: when exporting the frontend, set the API_URL environment variable
to the ingress hostname of the backend service.
**Important**: when exporting the frontend, set the `REFLEX_API_URL` environment
variable to the ingress hostname of the backend service.

```bash
REFLEX_API_URL=https://backend.example.com reflex export --frontend-only --no-zip
```

The exported files are in `.web/build/client`. Omit `--no-zip` to get a
`frontend.zip` instead.

If you will host the frontend from a path other than the root, set the
`REFLEX_FRONTEND_PATH` environment variable appropriately when exporting the frontend.
Expand All @@ -67,26 +85,25 @@ The following sections are currently a work in progress and may be incomplete.

### Azure

In the Azure load balancer, per-message deflate is not supported. Add the following
to your `rxconfig.py` to workaround this issue.
#### Static Web App

```python
import uvicorn.workers
Deploy the exported frontend with the Static Web Apps CLI:

import reflex as rx
```bash
npx @azure/static-web-apps-cli deploy --env production --app-location .web/build/client
```

For dynamic routes to work, add `staticwebapp.config.json` to `.web/build/client`
so 404s are served from `/404.html`:

class NoWSPerMessageDeflate(uvicorn.workers.UvicornH11Worker):
CONFIG_KWARGS = {
**uvicorn.workers.UvicornH11Worker.CONFIG_KWARGS,
"ws_per_message_deflate": False,
```json
{
"responseOverrides": {
"404": {
"rewrite": "/404.html"
}


config = rx.Config(
app_name="my_app",
gunicorn_worker_class="rxconfig.NoWSPerMessageDeflate",
)
}
}
```

#### Persistent Storage
Expand Down
5 changes: 0 additions & 5 deletions docker-example/production-app-platform/.dockerignore

This file was deleted.

65 changes: 0 additions & 65 deletions docker-example/production-app-platform/Dockerfile

This file was deleted.

8 changes: 6 additions & 2 deletions docker-example/production-compose/.dockerignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
.web
.git
__pycache__/*
.venv
__pycache__
*.py[cod]
.states
*.db
uploaded_files
Dockerfile
Caddy.Dockerfile
compose.yaml
compose.*.yaml
uploaded_files
7 changes: 4 additions & 3 deletions docker-example/production-compose/Caddy.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
FROM library/caddy
FROM caddy:2

COPY --from=local/reflex-app /app/.web/build/client /srv
ADD Caddyfile /etc/caddy/Caddyfile
# The `app` build context is the app service image (see compose.yaml).
COPY --from=app /app/.web/build/client /srv
COPY Caddyfile /etc/caddy/Caddyfile
7 changes: 5 additions & 2 deletions docker-example/production-compose/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

encode gzip

@backend_routes path /_event/* /ping /_upload /_upload/*
@backend_routes path /_event/* /ping /_health /_upload /_upload/*
handle @backend_routes {
reverse_proxy app:8000
}

root * /srv
route {
try_files {path} {path}/ /404.html
file_server
# Reflex writes a pre-compressed copy of every asset at export time.
file_server {
precompressed br zstd gzip
}
}
67 changes: 30 additions & 37 deletions docker-example/production-compose/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,52 +1,45 @@
# This docker file is intended to be used with docker compose to deploy a production
# instance of a Reflex app.
# check=skip=JSONArgsRecommended
# Backend image for the docker compose deployment. The frontend is exported
# here too, so the Caddy image can copy it out (see Caddy.Dockerfile).

# Stage 1: init
FROM python:3.13 as init
FROM python:3.13-slim AS builder

ARG uv=/root/.local/bin/uv
# uv installs python packages; reflex uses a bun found on PATH instead of downloading its own.
COPY --from=ghcr.io/astral-sh/uv:0.12 /uv /bin/uv
COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
ENV UV_COMPILE_BYTECODE=1 UV_NO_CACHE=1 PATH="/app/.venv/bin:$PATH"

# Install `uv` for faster package bootstrapping
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
RUN /install.sh && rm /install.sh
WORKDIR /app

# Install python requirements first so app edits do not reinstall them.
COPY requirements.txt .
RUN uv venv && uv pip install -r requirements.txt

# Copy local context to `/app` inside container (see .dockerignore)
WORKDIR /app
COPY . .
RUN mkdir -p /app/data /app/uploaded_files

# Create virtualenv which will be copied into final container
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN $uv venv
# Compile the app and build the static frontend. The cache mount keeps bun's
# package cache between builds so unchanged dependencies are not downloaded again.
RUN --mount=type=cache,target=/root/.bun/install/cache \
reflex export --frontend-only --no-zip

# Install app requirements and reflex inside virtualenv
RUN $uv pip install -r requirements.txt

# Deploy templates and prepare app
RUN reflex init

# Export static copy of frontend to /app/.web/build/client
RUN reflex export --frontend-only --no-zip
# Final image: the python environment, the app source, the static frontend, and
# .web/backend so the backend only evaluates stateful pages at startup.
FROM python:3.13-slim

# Copy static files out of /app to save space in backend image
RUN mv .web/build/client /tmp/client
RUN rm -rf .web && mkdir -p .web/build
RUN mv /tmp/client .web/build/client
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1

# Stage 2: copy artifacts into slim image
FROM python:3.13-slim
WORKDIR /app
RUN adduser --disabled-password --home /app reflex
COPY --chown=reflex --from=init /app /app
# Install libpq-dev for psycopg (skip if not using postgres).
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
# The app user needs to own /app itself so reflex can create .states there.
RUN adduser --disabled-password --home /app reflex && chown reflex /app
COPY --chown=reflex --from=builder /app/.venv .venv
COPY --chown=reflex --from=builder /app/.web/backend .web/backend
COPY --chown=reflex --from=builder /app/.web/build/client .web/build/client
COPY --chown=reflex . .
USER reflex
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1

# Needed until Reflex properly passes SIGTERM on backend.
STOPSIGNAL SIGKILL
RUN mkdir -p data uploaded_files

# Always apply migrations before starting the backend.
CMD [ -d alembic ] && reflex db migrate; \
# Apply migrations before starting the backend; a failed migration stops the container.
CMD if [ -d alembic ]; then reflex db migrate; fi && \
exec reflex run --env prod --backend-only
Loading
Loading