docker-example overhaul (#2690)
* docker-example overhaul Update docker-example with a more realistic multi-compose deployment, and also a more simplistic single-image deploy. * Use `uv` for faster bootstrapping * Separate simple and production-ready docker files * Split compose.yaml into 3 parts * Persist sqlite db and tls keys * Include postgres and redis * Include Adminer and redis-commander for adminstration * Suppose upload persistence * Update documentation * Update Caddyfile for compatibility with new Upload API * Simple Dockerfile: keep `reflex export --frontend-only` Pre-pack the resulting image with npm dependencies to reduce startup time * Simplify simple docker file to just use pip
This commit is contained in:
parent
ebd84b39f8
commit
e8faec708e
@ -1,2 +1,8 @@
|
||||
.web
|
||||
__pycache__/*
|
||||
.git
|
||||
__pycache__/*
|
||||
Dockerfile
|
||||
Caddy.Dockerfile
|
||||
compose.yaml
|
||||
compose.*.yaml
|
||||
uploaded_files
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
encode gzip
|
||||
|
||||
@backend_routes path /_event/* /_upload /ping
|
||||
@backend_routes path /_event/* /ping /_upload /_upload/*
|
||||
handle @backend_routes {
|
||||
reverse_proxy app:8000
|
||||
}
|
||||
|
@ -1,39 +1,21 @@
|
||||
# Stage 1: init
|
||||
FROM python:3.11 as init
|
||||
|
||||
# Pass `--build-arg API_URL=http://app.example.com:8000` during build
|
||||
ARG API_URL
|
||||
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
|
||||
FROM python:3.11
|
||||
|
||||
# Copy local context to `/app` inside container (see .dockerignore)
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Create virtualenv which will be copied into final container
|
||||
ENV VIRTUAL_ENV=/app/.venv
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN python3.11 -m venv $VIRTUAL_ENV
|
||||
|
||||
# Install app requirements and reflex inside virtualenv
|
||||
# Install app requirements and reflex in the container
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Deploy templates and prepare app
|
||||
RUN reflex init
|
||||
|
||||
# Export static copy of frontend to /app/.web/_static
|
||||
# Download all npm dependencies and compile and frontend
|
||||
RUN reflex export --frontend-only --no-zip
|
||||
|
||||
# Copy static files out of /app to save space in backend image
|
||||
RUN mv .web/_static /tmp/_static
|
||||
RUN rm -rf .web && mkdir .web
|
||||
RUN mv /tmp/_static .web/_static
|
||||
# Needed until Reflex properly passes SIGTERM on backend.
|
||||
STOPSIGNAL SIGKILL
|
||||
|
||||
# Stage 2: copy artifacts into slim image
|
||||
FROM python:3.11-slim
|
||||
ARG API_URL
|
||||
WORKDIR /app
|
||||
RUN adduser --disabled-password --home /app reflex
|
||||
COPY --chown=reflex --from=init /app /app
|
||||
USER reflex
|
||||
ENV PATH="/app/.venv/bin:$PATH" API_URL=$API_URL
|
||||
|
||||
CMD reflex db migrate && reflex run --env prod --backend-only
|
||||
# Always apply migrations before starting the backend.
|
||||
CMD [ -d alembic ] && reflex db migrate; reflex run --env prod
|
||||
|
@ -9,36 +9,59 @@ Reflex framework. If you use additional packages in your project you have to add
|
||||
this in the `requirements.txt` first. Copy the `Dockerfile`, `.dockerignore` and
|
||||
the `requirements.txt` file in your project folder.
|
||||
|
||||
## Build Reflex Container Image
|
||||
## Build Simple Reflex Container Image
|
||||
|
||||
The main `Dockerfile` is intended to build a very simple, single container deployment that runs
|
||||
the Reflex frontend and backend together, exposing ports 3000 and 8000.
|
||||
|
||||
To build your container image run the following command:
|
||||
|
||||
```bash
|
||||
docker build -t reflex-app:latest . --build-arg API_URL=http://app.example.com:8000
|
||||
docker build -t reflex-app:latest .
|
||||
```
|
||||
|
||||
Ensure that `API_URL` is set to the publicly accessible hostname or IP where the app
|
||||
will be hosted.
|
||||
|
||||
## Start Container Service
|
||||
|
||||
Finally, you can start your Reflex container service as follows:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:3000 -p 8000:8000 --name app reflex-app:latest
|
||||
docker run -it --rm -p 3000:3000 -p 8000:8000 --name app reflex-app:latest
|
||||
```
|
||||
|
||||
It may take a few seconds for the service to become available.
|
||||
|
||||
Access your app at http://localhost:3000.
|
||||
|
||||
Note that this container has _no persistence_ and will lose all data when
|
||||
stopped. You can use bind mounts or named volumes to persist the database and
|
||||
uploaded_files directories as needed.
|
||||
|
||||
# Production Service with Docker Compose and Caddy
|
||||
|
||||
An example production deployment uses automatic TLS with Caddy serving static files
|
||||
for the frontend and proxying requests to both the frontend and backend.
|
||||
|
||||
Copy `compose.yaml`, `Caddy.Dockerfile` and `Caddyfile` to your project directory. The production
|
||||
build leverages the same `Dockerfile` described above.
|
||||
Copy the following files to your project directory:
|
||||
* `compose.yaml`
|
||||
* `compose.prod.yaml`
|
||||
* `compose.tools.yaml`
|
||||
* `prod.Dockerfile`
|
||||
* `Caddy.Dockerfile`
|
||||
* `Caddyfile`
|
||||
|
||||
## Customize `Caddyfile`
|
||||
The production app container, based on `prod.Dockerfile`, builds and exports the
|
||||
frontend statically (to be served by Caddy). The resulting image only runs the
|
||||
backend service.
|
||||
|
||||
The `webserver` service, based on `Caddy.Dockerfile`, copies the static frontend
|
||||
and `Caddyfile` into the container to configure the reverse proxy routes that will
|
||||
forward requests to the backend service. Caddy will automatically provision TLS
|
||||
for localhost or the domain specified in the environment variable `DOMAIN`.
|
||||
|
||||
This type of deployment should use less memory and be more performant since
|
||||
nodejs is not required at runtime.
|
||||
|
||||
## Customize `Caddyfile` (optional)
|
||||
|
||||
If the app uses additional backend API routes, those should be added to the
|
||||
`@backend_routes` path matcher to ensure they are forwarded to the backend.
|
||||
@ -46,15 +69,16 @@ If the app uses additional backend API routes, those should be added to the
|
||||
## Build Reflex Production Service
|
||||
|
||||
During build, set `DOMAIN` environment variable to the domain where the app will
|
||||
be hosted! (Do not include http or https, it will always use https)
|
||||
be hosted! (Do not include http or https, it will always use https).
|
||||
|
||||
**If `DOMAIN` is not provided, the service will default to `localhost`.**
|
||||
|
||||
```bash
|
||||
DOMAIN=example.com docker compose build
|
||||
```
|
||||
|
||||
This will build both the `app` service from the existing `Dockerfile` and the `webserver`
|
||||
service via `Caddy.Dockerfile` that copies the `Caddyfile` and static frontend export
|
||||
from the `app` service into the container.
|
||||
This will build both the `app` service from the `prod.Dockerfile` and the `webserver`
|
||||
service via `Caddy.Dockerfile`.
|
||||
|
||||
## Run Reflex Production Service
|
||||
|
||||
@ -64,3 +88,32 @@ DOMAIN=example.com docker compose up
|
||||
|
||||
The app should be available at the specified domain via HTTPS. Certificate
|
||||
provisioning will occur automatically and may take a few minutes.
|
||||
|
||||
### Data Persistence
|
||||
|
||||
Named docker volumes are used to persist the app database (`db-data`),
|
||||
uploaded_files (`upload-data`), and caddy TLS keys and certificates
|
||||
(`caddy-data`).
|
||||
|
||||
## More Robust Deployment
|
||||
|
||||
For a more robust deployment, consider bringing the service up with
|
||||
`compose.prod.yaml` which includes postgres database and redis cache, allowing
|
||||
the backend to run with multiple workers and service more requests.
|
||||
|
||||
```bash
|
||||
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml up -d
|
||||
```
|
||||
|
||||
Postgres uses its own named docker volume for data persistence.
|
||||
|
||||
## Admin Tools
|
||||
|
||||
When needed, the services in `compose.tools.yaml` can be brought up, providing
|
||||
graphical database administration (Adminer on http://localhost:8080) and a
|
||||
redis cache browser (redis-commander on http://localhost:8081). It is not recommended
|
||||
to deploy these services if they are not in active use.
|
||||
|
||||
```bash
|
||||
DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
|
||||
```
|
25
docker-example/compose.prod.yaml
Normal file
25
docker-example/compose.prod.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
# Use this override file to run the app in prod mode with postgres and redis
|
||||
# docker compose -f compose.yaml -f compose.prod.yaml up -d
|
||||
services:
|
||||
db:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: secret
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
restart: always
|
||||
|
||||
app:
|
||||
environment:
|
||||
DB_URL: postgresql+psycopg2://postgres:secret@db/postgres
|
||||
REDIS_URL: redis://redis:6379
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
18
docker-example/compose.tools.yaml
Normal file
18
docker-example/compose.tools.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
# Use this override file with `compose.prod.yaml` to run admin tools
|
||||
# for production services.
|
||||
# docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
|
||||
services:
|
||||
adminer:
|
||||
image: adminer
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
||||
redis-commander:
|
||||
image: ghcr.io/joeferner/redis-commander:latest
|
||||
environment:
|
||||
- REDIS_HOSTS=local:redis:6379
|
||||
ports:
|
||||
- "8081:8081"
|
||||
|
||||
volumes:
|
||||
redis-ui-settings:
|
@ -1,21 +1,42 @@
|
||||
# Base compose file production deployment of reflex app with Caddy webserver
|
||||
# providing TLS termination and reverse proxying.
|
||||
#
|
||||
# See `compose.prod.yaml` for more robust and performant deployment option.
|
||||
#
|
||||
# During build and run, set environment DOMAIN pointing
|
||||
# to publicly accessible domain where app will be hosted
|
||||
services:
|
||||
app:
|
||||
image: local/reflex-app
|
||||
environment:
|
||||
DB_URL: sqlite:///data/reflex.db
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
API_URL: https://${DOMAIN:-localhost}
|
||||
dockerfile: prod.Dockerfile
|
||||
volumes:
|
||||
- db-data:/app/data
|
||||
- upload-data:/app/uploaded_files
|
||||
restart: always
|
||||
|
||||
webserver:
|
||||
environment:
|
||||
DOMAIN: ${DOMAIN:-localhost}
|
||||
ports:
|
||||
- 443:443
|
||||
- 80:80 # for acme-challenge via HTTP
|
||||
- 80:80 # For acme-challenge via HTTP.
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Caddy.Dockerfile
|
||||
volumes:
|
||||
- caddy-data:/root/.caddy
|
||||
restart: always
|
||||
depends_on:
|
||||
- app
|
||||
- app
|
||||
|
||||
volumes:
|
||||
# SQLite data
|
||||
db-data:
|
||||
# Uploaded files
|
||||
upload-data:
|
||||
# TLS keys and certificates
|
||||
caddy-data:
|
51
docker-example/prod.Dockerfile
Normal file
51
docker-example/prod.Dockerfile
Normal file
@ -0,0 +1,51 @@
|
||||
# This docker file is intended to be used with docker compose to deploy a production
|
||||
# instance of a Reflex app.
|
||||
|
||||
# Stage 1: init
|
||||
FROM python:3.11 as init
|
||||
|
||||
ARG uv=/root/.cargo/bin/uv
|
||||
|
||||
# Install `uv` for faster package boostrapping
|
||||
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
|
||||
RUN /install.sh && rm /install.sh
|
||||
|
||||
# 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
|
||||
|
||||
# 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/_static
|
||||
RUN reflex export --frontend-only --no-zip
|
||||
|
||||
# Copy static files out of /app to save space in backend image
|
||||
RUN mv .web/_static /tmp/_static
|
||||
RUN rm -rf .web && mkdir .web
|
||||
RUN mv /tmp/_static .web/_static
|
||||
|
||||
# Stage 2: copy artifacts into slim image
|
||||
FROM python:3.11-slim
|
||||
WORKDIR /app
|
||||
RUN adduser --disabled-password --home /app reflex
|
||||
COPY --chown=reflex --from=init /app /app
|
||||
# Install libpq-dev for psycopg2 (skip if not using postgres).
|
||||
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
|
||||
USER reflex
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
# Needed until Reflex properly passes SIGTERM on backend.
|
||||
STOPSIGNAL SIGKILL
|
||||
|
||||
# Always apply migrations before starting the backend.
|
||||
CMD reflex db migrate && reflex run --env prod --backend-only
|
Loading…
Reference in New Issue
Block a user