Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docker-example overhaul #2690

Merged
merged 3 commits into from
Mar 5, 2024
Merged
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
8 changes: 7 additions & 1 deletion docker-example/.dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
.web
__pycache__/*
.git
__pycache__/*
Dockerfile
Caddy.Dockerfile
compose.yaml
compose.*.yaml
uploaded_files
2 changes: 1 addition & 1 deletion docker-example/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
34 changes: 8 additions & 26 deletions docker-example/Dockerfile
Original file line number Diff line number Diff line change
@@ -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

# 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
# Needed until Reflex properly passes SIGTERM on backend.
STOPSIGNAL SIGKILL

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
79 changes: 66 additions & 13 deletions docker-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,76 @@ 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`

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.

## Customize `Caddyfile`
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.

## 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

Expand All @@ -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 changes: 25 additions & 0 deletions docker-example/compose.prod.yaml
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions docker-example/compose.tools.yaml
Original file line number Diff line number Diff line change
@@ -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:
29 changes: 25 additions & 4 deletions docker-example/compose.yaml
Original file line number Diff line number Diff line change
@@ -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 changes: 51 additions & 0 deletions docker-example/prod.Dockerfile
Original file line number Diff line number Diff line change
@@ -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