Skip to content

Commit 2a7b963

Browse files
committed
Update docker-example
Improvements: * use `exec` when starting `reflex` to make it PID 1 in the container * include `redis-server` in the simple containers for better concurrency Create 4 separate examples with separate README files for different use cases: * simple-two-port: this is the same as the previous Dockerfile, except with redis * simple-one-port: this is the same as app.Dockerfile, but with the Caddyfile pulled out and redis included * production-compose: for deployment on a standalone VPS * production-app-platform: for deployment on AWS, Azure, GCE, etc. This deployment expects the frontend, redis, and database to be managed separately depending on the platform.
1 parent d96baac commit 2a7b963

File tree

20 files changed

+398
-151
lines changed

20 files changed

+398
-151
lines changed

β€Ždocker-example/README.md

Lines changed: 20 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,30 @@
1-
# Reflex Docker Container
1+
# Reflex Docker Examples
22

3-
This example describes how to create and use a container image for Reflex with your own code.
3+
This directory contains several examples of how to deploy Reflex apps using docker.
44

5-
## Update Requirements
5+
In all cases, ensure that your `requirements.txt` file is up to date and
6+
includes the `reflex` package.
67

7-
The `requirements.txt` includes the reflex package which is needed to install
8-
Reflex framework. If you use additional packages in your project you have to add
9-
this in the `requirements.txt` first. Copy the `Dockerfile`, `.dockerignore` and
10-
the `requirements.txt` file in your project folder.
8+
## `simple-two-port`
119

12-
## Build Simple Reflex Container Image
10+
The most basic production deployment exposes two HTTP ports and relies on an
11+
existing load balancer to forward the traffic appropriately.
1312

14-
The main `Dockerfile` is intended to build a very simple, single container deployment that runs
15-
the Reflex frontend and backend together, exposing ports 3000 and 8000.
13+
## `simple-one-port`
1614

17-
To build your container image run the following command:
15+
This deployment exports the frontend statically and serves it via a single HTTP
16+
port using Caddy. This is useful for platforms that only support a single port
17+
or where running a node server in the container is undesirable.
1818

19-
```bash
20-
docker build -t reflex-app:latest .
21-
```
19+
## `production-compose`
2220

23-
## Start Container Service
21+
This deployment is intended for use with a standalone VPS that is only hosting a
22+
single Reflex app. It provides the entire stack in a single `compose.yaml`
23+
including a webserver, one or more backend instances, redis, and a postgres
24+
database.
2425

25-
Finally, you can start your Reflex container service as follows:
26+
## `production-app-platform`
2627

27-
```bash
28-
docker run -it --rm -p 3000:3000 -p 8000:8000 --name app reflex-app:latest
29-
```
30-
31-
It may take a few seconds for the service to become available.
32-
33-
Access your app at http://localhost:3000.
34-
35-
Note that this container has _no persistence_ and will lose all data when
36-
stopped. You can use bind mounts or named volumes to persist the database and
37-
uploaded_files directories as needed.
38-
39-
# Production Service with Docker Compose and Caddy
40-
41-
An example production deployment uses automatic TLS with Caddy serving static files
42-
for the frontend and proxying requests to both the frontend and backend.
43-
44-
Copy the following files to your project directory:
45-
* `compose.yaml`
46-
* `compose.prod.yaml`
47-
* `compose.tools.yaml`
48-
* `prod.Dockerfile`
49-
* `Caddy.Dockerfile`
50-
* `Caddyfile`
51-
52-
The production app container, based on `prod.Dockerfile`, builds and exports the
53-
frontend statically (to be served by Caddy). The resulting image only runs the
54-
backend service.
55-
56-
The `webserver` service, based on `Caddy.Dockerfile`, copies the static frontend
57-
and `Caddyfile` into the container to configure the reverse proxy routes that will
58-
forward requests to the backend service. Caddy will automatically provision TLS
59-
for localhost or the domain specified in the environment variable `DOMAIN`.
60-
61-
This type of deployment should use less memory and be more performant since
62-
nodejs is not required at runtime.
63-
64-
## Customize `Caddyfile` (optional)
65-
66-
If the app uses additional backend API routes, those should be added to the
67-
`@backend_routes` path matcher to ensure they are forwarded to the backend.
68-
69-
## Build Reflex Production Service
70-
71-
During build, set `DOMAIN` environment variable to the domain where the app will
72-
be hosted! (Do not include http or https, it will always use https).
73-
74-
**If `DOMAIN` is not provided, the service will default to `localhost`.**
75-
76-
```bash
77-
DOMAIN=example.com docker compose build
78-
```
79-
80-
This will build both the `app` service from the `prod.Dockerfile` and the `webserver`
81-
service via `Caddy.Dockerfile`.
82-
83-
## Run Reflex Production Service
84-
85-
```bash
86-
DOMAIN=example.com docker compose up
87-
```
88-
89-
The app should be available at the specified domain via HTTPS. Certificate
90-
provisioning will occur automatically and may take a few minutes.
91-
92-
### Data Persistence
93-
94-
Named docker volumes are used to persist the app database (`db-data`),
95-
uploaded_files (`upload-data`), and caddy TLS keys and certificates
96-
(`caddy-data`).
97-
98-
## More Robust Deployment
99-
100-
For a more robust deployment, consider bringing the service up with
101-
`compose.prod.yaml` which includes postgres database and redis cache, allowing
102-
the backend to run with multiple workers and service more requests.
103-
104-
```bash
105-
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml up -d
106-
```
107-
108-
Postgres uses its own named docker volume for data persistence.
109-
110-
## Admin Tools
111-
112-
When needed, the services in `compose.tools.yaml` can be brought up, providing
113-
graphical database administration (Adminer on http://localhost:8080) and a
114-
redis cache browser (redis-commander on http://localhost:8081). It is not recommended
115-
to deploy these services if they are not in active use.
116-
117-
```bash
118-
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
119-
```
120-
121-
# Container Hosting
122-
123-
Most container hosting services automatically terminate TLS and expect the app
124-
to be listening on a single port (typically `$PORT`).
125-
126-
To host a Reflex app on one of these platforms, like Google Cloud Run, Render,
127-
Railway, etc, use `app.Dockerfile` to build a single image containing a reverse
128-
proxy that will serve that frontend as static files and proxy requests to the
129-
backend for specific endpoints.
130-
131-
If the chosen platform does not support buildx and thus heredoc, you can copy
132-
the Caddyfile configuration into a separate Caddyfile in the root of the
133-
project.
28+
This example deployment is intended for use with App hosting platforms, like
29+
Azure, AWS, or Google Cloud Run. It is the backend of the deployment, which
30+
depends on a separately hosted redis instance and static frontend deployment.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.web
2+
.git
3+
__pycache__/*
4+
Dockerfile
5+
uploaded_files
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# This docker file is intended to be used with container hosting services
2+
#
3+
# After deploying this image, get the URL pointing to the backend service
4+
# and run API_URL=https://path-to-my-container.example.com reflex export frontend
5+
# then copy the contents of `frontend.zip` to your static file server (github pages, s3, etc).
6+
#
7+
# Azure Static Web App example:
8+
# npx @azure/static-web-apps-cli deploy --env production --app-location .web/_static
9+
#
10+
# For dynamic routes to function properly, ensure that 404s are redirected to /404 on the
11+
# static file host (for github pages, this works out of the box; remember to create .nojekyll).
12+
#
13+
# For azure static web apps, add `staticwebapp.config.json` to to `.web/_static` with the following:
14+
# {
15+
# "responseOverrides": {
16+
# "404": {
17+
# "rewrite": "/404.html"
18+
# }
19+
# }
20+
# }
21+
#
22+
# Note: many container hosting platforms require amd64 images, so when building on an M1 Mac
23+
# for example, pass `docker build --platform=linux/amd64 ...`
24+
25+
# Stage 1: init
26+
FROM python:3.11 as init
27+
28+
ARG uv=/root/.cargo/bin/uv
29+
30+
# Install `uv` for faster package boostrapping
31+
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
32+
RUN /install.sh && rm /install.sh
33+
34+
# Copy local context to `/app` inside container (see .dockerignore)
35+
WORKDIR /app
36+
COPY . .
37+
RUN mkdir -p /app/data /app/uploaded_files
38+
39+
# Create virtualenv which will be copied into final container
40+
ENV VIRTUAL_ENV=/app/.venv
41+
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
42+
RUN $uv venv
43+
44+
# Install app requirements and reflex inside virtualenv
45+
RUN $uv pip install -r requirements.txt
46+
47+
# Deploy templates and prepare app
48+
RUN reflex init
49+
50+
# Stage 2: copy artifacts into slim image
51+
FROM python:3.11-slim
52+
WORKDIR /app
53+
RUN adduser --disabled-password --home /app reflex
54+
COPY --chown=reflex --from=init /app /app
55+
# Install libpq-dev for psycopg2 (skip if not using postgres).
56+
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
57+
USER reflex
58+
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
59+
60+
# Needed until Reflex properly passes SIGTERM on backend.
61+
STOPSIGNAL SIGKILL
62+
63+
# Always apply migrations before starting the backend.
64+
CMD [ -d alembic ] && reflex db migrate; \
65+
exec reflex run --env prod --backend-only --backend-port ${PORT:-8000}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# production-app-platform
2+
3+
This example deployment is intended for use with App hosting platforms, like
4+
Azure, AWS, or Google Cloud Run.
5+
6+
## Architecture
7+
8+
The production deployment consists of a few pieces:
9+
* Backend container - built by `Dockerfile` Runs the Reflex backend
10+
service on port 8000 and is scalable to multiple instances.
11+
* Redis container - A single instance the standard `redis` docker image should
12+
share private networking with the backend
13+
* Static frontend - HTML/CSS/JS files that are hosted via a CDN or static file
14+
server. This is not included in the docker image.
15+
16+
## Deployment
17+
18+
These general steps do not cover the specifics of each platform, but all platforms should
19+
support the concepts described here.
20+
21+
### Vnet
22+
23+
All containers in the deployment should be hooked up to the same virtual private
24+
network so they can access the redis service and optionally the database server.
25+
The vnet should not be exposed to the internet, use an ingress rule to terminate
26+
TLS at the load balancer and forward the traffic to a backend service replica.
27+
28+
### Redis
29+
30+
Deploy a `redis` instance on the vnet.
31+
32+
### Backend
33+
34+
The backend is built by the `Dockerfile` in this directory. When deploying the
35+
backend, be sure to set REDIS_URL=redis://internal-redis-hostname to connect to
36+
the redis service.
37+
38+
### Ingress
39+
40+
Configure the load balancer for the app to forward traffic to port 8000 on the
41+
backend service replicas. Most platforms will generate an ingress hostname
42+
automatically. Make sure when you access the ingress endpoint on `/ping` that it
43+
returns "pong", indicating that the backend is up an available.
44+
45+
### Frontend
46+
47+
The frontend should be hosted on a static file server or CDN.
48+
49+
**Important**: when exporting the frontend, set the API_URL environment variable
50+
to the ingress hostname of the backend service.
51+
52+
If you will host the frontend from a path other than the root, set the
53+
`FRONTEND_PATH` environment variable appropriately when exporting the frontend.
54+
55+
Most static hosts will automatically use the `/404.html` file to handle 404
56+
errors. _This is essential for dynamic routes to work correctly._ Ensure that
57+
missing routes return the `/404.html` content to the user if this is not the
58+
default behavior.
59+
60+
_For Github Pages_: ensure the file `.nojekyll` is present in the root of the repo
61+
to avoid special processing of underscore-prefix directories, like `_next`.
62+
63+
## Platform Notes
64+
65+
The following sections are currently a work in progress and may be incomplete.
66+
67+
### Azure
68+
69+
In the Azure load balancer, per-message deflate is not supported. Add the following
70+
to your `rxconfig.py` to workaround this issue.
71+
72+
```python
73+
import uvicorn.workers
74+
75+
import reflex as rx
76+
77+
78+
class NoWSPerMessageDeflate(uvicorn.workers.UvicornH11Worker):
79+
CONFIG_KWARGS = {
80+
**uvicorn.workers.UvicornH11Worker.CONFIG_KWARGS,
81+
"ws_per_message_deflate": False,
82+
}
83+
84+
85+
config = rx.Config(
86+
app_name="my_app",
87+
gunicorn_worker_class="rxconfig.NoWSPerMessageDeflate",
88+
)
89+
```
90+
91+
#### Persistent Storage
92+
93+
If you need to use a database or upload files, you cannot save them to the
94+
container volume. Use Azure Files and mount it into the container at /app/uploaded_files.
95+
96+
#### Resource Types
97+
98+
* Create a new vnet with 10.0.0.0/16
99+
* Create a new subnet for redis, database, and containers
100+
* Deploy redis as a Container Instances
101+
* Deploy database server as "Azure Database for PostgreSQL"
102+
* Create a new database for the app
103+
* Set db-url as a secret containing the db user/password connection string
104+
* Deploy Storage account for uploaded files
105+
* Enable access from the vnet and container subnet
106+
* Create a new file share
107+
* In the environment, create a new files share (get the storage key)
108+
* Deploy the backend as a Container App
109+
* Create a custom Container App Environment linked up to the same vnet as the redis container.
110+
* Set REDIS_URL and DB_URL environment variables
111+
* Add the volume from the environment
112+
* Add the volume mount to the container
113+
* Deploy the frontend as a Static Web App

β€Ždocker-example/prod.Dockerfile renamed to β€Ždocker-example/production-compose/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ COPY --chown=reflex --from=init /app /app
4242
# Install libpq-dev for psycopg2 (skip if not using postgres).
4343
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
4444
USER reflex
45-
ENV PATH="/app/.venv/bin:$PATH"
45+
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1
4646

4747
# Needed until Reflex properly passes SIGTERM on backend.
4848
STOPSIGNAL SIGKILL
4949

5050
# Always apply migrations before starting the backend.
51-
CMD reflex db migrate && reflex run --env prod --backend-only
51+
CMD [ -d alembic ] && reflex db migrate; \
52+
exec reflex run --env prod --backend-only

0 commit comments

Comments
Β (0)