Add production-one-port example (#4489)
* Add production-one-port example A more complex version of simple-one-port that facilitates better layer caching to shorten build times and multi-stage build to reduce final image size. Harder to understand, but ultimately nicer to use. * fix Caddyfile format to avoid complaints * docker-examples: bump all base images to python:3.13
This commit is contained in:
parent
9ff386bf48
commit
0a34949019
@ -23,7 +23,7 @@
|
|||||||
# for example, pass `docker build --platform=linux/amd64 ...`
|
# for example, pass `docker build --platform=linux/amd64 ...`
|
||||||
|
|
||||||
# Stage 1: init
|
# Stage 1: init
|
||||||
FROM python:3.11 as init
|
FROM python:3.13 as init
|
||||||
|
|
||||||
ARG uv=/root/.local/bin/uv
|
ARG uv=/root/.local/bin/uv
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ RUN $uv pip install -r requirements.txt
|
|||||||
RUN reflex init
|
RUN reflex init
|
||||||
|
|
||||||
# Stage 2: copy artifacts into slim image
|
# Stage 2: copy artifacts into slim image
|
||||||
FROM python:3.11-slim
|
FROM python:3.13-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN adduser --disabled-password --home /app reflex
|
RUN adduser --disabled-password --home /app reflex
|
||||||
COPY --chown=reflex --from=init /app /app
|
COPY --chown=reflex --from=init /app /app
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# instance of a Reflex app.
|
# instance of a Reflex app.
|
||||||
|
|
||||||
# Stage 1: init
|
# Stage 1: init
|
||||||
FROM python:3.11 as init
|
FROM python:3.13 as init
|
||||||
|
|
||||||
ARG uv=/root/.local/bin/uv
|
ARG uv=/root/.local/bin/uv
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ RUN rm -rf .web && mkdir .web
|
|||||||
RUN mv /tmp/_static .web/_static
|
RUN mv /tmp/_static .web/_static
|
||||||
|
|
||||||
# Stage 2: copy artifacts into slim image
|
# Stage 2: copy artifacts into slim image
|
||||||
FROM python:3.11-slim
|
FROM python:3.13-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN adduser --disabled-password --home /app reflex
|
RUN adduser --disabled-password --home /app reflex
|
||||||
COPY --chown=reflex --from=init /app /app
|
COPY --chown=reflex --from=init /app /app
|
||||||
|
3
docker-example/production-one-port/.dockerignore
Normal file
3
docker-example/production-one-port/.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.web
|
||||||
|
!.web/bun.lockb
|
||||||
|
!.web/package.json
|
14
docker-example/production-one-port/Caddyfile
Normal file
14
docker-example/production-one-port/Caddyfile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
:{$PORT}
|
||||||
|
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
@backend_routes path /_event/* /ping /_upload /_upload/*
|
||||||
|
handle @backend_routes {
|
||||||
|
reverse_proxy localhost:8000
|
||||||
|
}
|
||||||
|
|
||||||
|
root * /srv
|
||||||
|
route {
|
||||||
|
try_files {path} {path}/ /404.html
|
||||||
|
file_server
|
||||||
|
}
|
62
docker-example/production-one-port/Dockerfile
Normal file
62
docker-example/production-one-port/Dockerfile
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# This Dockerfile is used to deploy a single-container Reflex app instance
|
||||||
|
# to services like Render, Railway, Heroku, GCP, and others.
|
||||||
|
|
||||||
|
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
||||||
|
ARG PORT=8080
|
||||||
|
# Only set for local/direct access. When TLS is used, the API_URL is assumed to be the same as the frontend.
|
||||||
|
ARG API_URL
|
||||||
|
|
||||||
|
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
||||||
|
# from a single exposed port, expecting TLS termination to be handled at the
|
||||||
|
# edge by the given platform.
|
||||||
|
FROM python:3.13 as builder
|
||||||
|
|
||||||
|
RUN mkdir -p /app/.web
|
||||||
|
RUN python -m venv /app/.venv
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install python app requirements and reflex in the container
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Install reflex helper utilities like bun/fnm/node
|
||||||
|
COPY rxconfig.py ./
|
||||||
|
RUN reflex init
|
||||||
|
|
||||||
|
# Install pre-cached frontend dependencies (if exist)
|
||||||
|
COPY *.web/bun.lockb *.web/package.json .web/
|
||||||
|
RUN if [ -f .web/bun.lockb ]; then cd .web && ~/.local/share/reflex/bun/bin/bun install --frozen-lockfile; fi
|
||||||
|
|
||||||
|
# Copy local context to `/app` inside container (see .dockerignore)
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ARG PORT API_URL
|
||||||
|
# Download other npm dependencies and compile frontend
|
||||||
|
RUN API_URL=${API_URL:-http://localhost:$PORT} reflex export --loglevel debug --frontend-only --no-zip && mv .web/_static/* /srv/ && rm -rf .web
|
||||||
|
|
||||||
|
|
||||||
|
# Final image with only necessary files
|
||||||
|
FROM python:3.13-slim
|
||||||
|
|
||||||
|
# Install Caddy and redis server inside image
|
||||||
|
RUN apt-get update -y && apt-get install -y caddy redis-server && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ARG PORT API_URL
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH" PORT=$PORT API_URL=${API_URL:-http://localhost:$PORT} REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app /app
|
||||||
|
COPY --from=builder /srv /srv
|
||||||
|
|
||||||
|
# Needed until Reflex properly passes SIGTERM on backend.
|
||||||
|
STOPSIGNAL SIGKILL
|
||||||
|
|
||||||
|
EXPOSE $PORT
|
||||||
|
|
||||||
|
# Apply migrations before starting the backend.
|
||||||
|
CMD [ -d alembic ] && reflex db migrate; \
|
||||||
|
caddy start && \
|
||||||
|
redis-server --daemonize yes && \
|
||||||
|
exec reflex run --env prod --backend-only
|
37
docker-example/production-one-port/README.md
Normal file
37
docker-example/production-one-port/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# production-one-port
|
||||||
|
|
||||||
|
This docker deployment runs Reflex in prod mode, exposing a single HTTP port:
|
||||||
|
* `8080` (`$PORT`) - Caddy server hosting the frontend statically and proxying requests to the backend.
|
||||||
|
|
||||||
|
The deployment also runs a local Redis server to store state for each user.
|
||||||
|
|
||||||
|
Conceptually it is similar to the `simple-one-port` example except it:
|
||||||
|
* has layer caching for python, reflex, and node dependencies
|
||||||
|
* uses multi-stage build to reduce the size of the final image
|
||||||
|
|
||||||
|
Using this method may be preferable for deploying in memory constrained
|
||||||
|
environments, because it serves a static frontend export, rather than running
|
||||||
|
the NextJS server via node.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker build -t reflex-production-one-port .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```console
|
||||||
|
docker run -p 8080:8080 reflex-production-one-port
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This container should be used with an existing load balancer or reverse proxy to
|
||||||
|
terminate TLS.
|
||||||
|
|
||||||
|
It is also useful for deploying to simple app platforms, such as Render or Heroku.
|
@ -11,4 +11,4 @@ root * /srv
|
|||||||
route {
|
route {
|
||||||
try_files {path} {path}/ /404.html
|
try_files {path} {path}/ /404.html
|
||||||
file_server
|
file_server
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
# It uses a reverse proxy to serve the frontend statically and proxy to backend
|
||||||
# from a single exposed port, expecting TLS termination to be handled at the
|
# from a single exposed port, expecting TLS termination to be handled at the
|
||||||
# edge by the given platform.
|
# edge by the given platform.
|
||||||
FROM python:3.11
|
FROM python:3.13
|
||||||
|
|
||||||
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
# If the service expects a different port, provide it here (f.e Render expects port 10000)
|
||||||
ARG PORT=8080
|
ARG PORT=8080
|
||||||
@ -38,4 +38,4 @@ EXPOSE $PORT
|
|||||||
CMD [ -d alembic ] && reflex db migrate; \
|
CMD [ -d alembic ] && reflex db migrate; \
|
||||||
caddy start && \
|
caddy start && \
|
||||||
redis-server --daemonize yes && \
|
redis-server --daemonize yes && \
|
||||||
exec reflex run --env prod --backend-only
|
exec reflex run --env prod --backend-only
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
|
# This Dockerfile is used to deploy a simple single-container Reflex app instance.
|
||||||
FROM python:3.12
|
FROM python:3.13
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y redis-server && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y redis-server && rm -rf /var/lib/apt/lists/*
|
||||||
ENV REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
ENV REDIS_URL=redis://localhost PYTHONUNBUFFERED=1
|
||||||
|
Loading…
Reference in New Issue
Block a user