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
|
.web
|
||||||
__pycache__/*
|
.git
|
||||||
|
__pycache__/*
|
||||||
|
Dockerfile
|
||||||
|
Caddy.Dockerfile
|
||||||
|
compose.yaml
|
||||||
|
compose.*.yaml
|
||||||
|
uploaded_files
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
encode gzip
|
encode gzip
|
||||||
|
|
||||||
@backend_routes path /_event/* /_upload /ping
|
@backend_routes path /_event/* /ping /_upload /_upload/*
|
||||||
handle @backend_routes {
|
handle @backend_routes {
|
||||||
reverse_proxy app:8000
|
reverse_proxy app:8000
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,21 @@
|
|||||||
# Stage 1: init
|
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
|
||||||
FROM python:3.11 as init
|
FROM python:3.11
|
||||||
|
|
||||||
# Pass `--build-arg API_URL=http://app.example.com:8000` during build
|
|
||||||
ARG API_URL
|
|
||||||
|
|
||||||
# Copy local context to `/app` inside container (see .dockerignore)
|
# Copy local context to `/app` inside container (see .dockerignore)
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Create virtualenv which will be copied into final container
|
# Install app requirements and reflex in the 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
|
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
# Deploy templates and prepare app
|
# Deploy templates and prepare app
|
||||||
RUN reflex init
|
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
|
RUN reflex export --frontend-only --no-zip
|
||||||
|
|
||||||
# Copy static files out of /app to save space in backend image
|
# Needed until Reflex properly passes SIGTERM on backend.
|
||||||
RUN mv .web/_static /tmp/_static
|
STOPSIGNAL SIGKILL
|
||||||
RUN rm -rf .web && mkdir .web
|
|
||||||
RUN mv /tmp/_static .web/_static
|
|
||||||
|
|
||||||
# Stage 2: copy artifacts into slim image
|
# Always apply migrations before starting the backend.
|
||||||
FROM python:3.11-slim
|
CMD [ -d alembic ] && reflex db migrate; reflex run --env prod
|
||||||
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
|
|
||||||
|
@ -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
|
this in the `requirements.txt` first. Copy the `Dockerfile`, `.dockerignore` and
|
||||||
the `requirements.txt` file in your project folder.
|
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:
|
To build your container image run the following command:
|
||||||
|
|
||||||
```bash
|
```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
|
## Start Container Service
|
||||||
|
|
||||||
Finally, you can start your Reflex container service as follows:
|
Finally, you can start your Reflex container service as follows:
|
||||||
|
|
||||||
```bash
|
```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.
|
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
|
# Production Service with Docker Compose and Caddy
|
||||||
|
|
||||||
An example production deployment uses automatic TLS with Caddy serving static files
|
An example production deployment uses automatic TLS with Caddy serving static files
|
||||||
for the frontend and proxying requests to both the frontend and backend.
|
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
|
Copy the following files to your project directory:
|
||||||
build leverages the same `Dockerfile` described above.
|
* `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
|
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.
|
`@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
|
## Build Reflex Production Service
|
||||||
|
|
||||||
During build, set `DOMAIN` environment variable to the domain where the app will
|
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
|
```bash
|
||||||
DOMAIN=example.com docker compose build
|
DOMAIN=example.com docker compose build
|
||||||
```
|
```
|
||||||
|
|
||||||
This will build both the `app` service from the existing `Dockerfile` and the `webserver`
|
This will build both the `app` service from the `prod.Dockerfile` and the `webserver`
|
||||||
service via `Caddy.Dockerfile` that copies the `Caddyfile` and static frontend export
|
service via `Caddy.Dockerfile`.
|
||||||
from the `app` service into the container.
|
|
||||||
|
|
||||||
## Run Reflex Production Service
|
## 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
|
The app should be available at the specified domain via HTTPS. Certificate
|
||||||
provisioning will occur automatically and may take a few minutes.
|
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
|
# During build and run, set environment DOMAIN pointing
|
||||||
# to publicly accessible domain where app will be hosted
|
# to publicly accessible domain where app will be hosted
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: local/reflex-app
|
image: local/reflex-app
|
||||||
|
environment:
|
||||||
|
DB_URL: sqlite:///data/reflex.db
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
dockerfile: prod.Dockerfile
|
||||||
API_URL: https://${DOMAIN:-localhost}
|
volumes:
|
||||||
|
- db-data:/app/data
|
||||||
|
- upload-data:/app/uploaded_files
|
||||||
|
restart: always
|
||||||
|
|
||||||
webserver:
|
webserver:
|
||||||
environment:
|
environment:
|
||||||
DOMAIN: ${DOMAIN:-localhost}
|
DOMAIN: ${DOMAIN:-localhost}
|
||||||
ports:
|
ports:
|
||||||
- 443:443
|
- 443:443
|
||||||
- 80:80 # for acme-challenge via HTTP
|
- 80:80 # For acme-challenge via HTTP.
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Caddy.Dockerfile
|
dockerfile: Caddy.Dockerfile
|
||||||
|
volumes:
|
||||||
|
- caddy-data:/root/.caddy
|
||||||
|
restart: always
|
||||||
depends_on:
|
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