浏览代码

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
Masen Furer 5 月之前
父节点
当前提交
0a34949019

+ 2 - 2
docker-example/production-app-platform/Dockerfile

@@ -23,7 +23,7 @@
 # for example, pass `docker build --platform=linux/amd64 ...`
 
 # Stage 1: init
-FROM python:3.11 as init
+FROM python:3.13 as init
 
 ARG uv=/root/.local/bin/uv
 
@@ -48,7 +48,7 @@ RUN $uv pip install -r requirements.txt
 RUN reflex init
 
 # Stage 2: copy artifacts into slim image 
-FROM python:3.11-slim
+FROM python:3.13-slim
 WORKDIR /app
 RUN adduser --disabled-password --home /app reflex
 COPY --chown=reflex --from=init /app /app

+ 2 - 2
docker-example/production-compose/Dockerfile

@@ -2,7 +2,7 @@
 # instance of a Reflex app.
 
 # Stage 1: init
-FROM python:3.11 as init
+FROM python:3.13 as init
 
 ARG uv=/root/.local/bin/uv
 
@@ -35,7 +35,7 @@ RUN rm -rf .web && mkdir .web
 RUN mv /tmp/_static .web/_static
 
 # Stage 2: copy artifacts into slim image 
-FROM python:3.11-slim
+FROM python:3.13-slim
 WORKDIR /app
 RUN adduser --disabled-password --home /app reflex
 COPY --chown=reflex --from=init /app /app

+ 3 - 0
docker-example/production-one-port/.dockerignore

@@ -0,0 +1,3 @@
+.web
+!.web/bun.lockb
+!.web/package.json

+ 14 - 0
docker-example/production-one-port/Caddyfile

@@ -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 - 0
docker-example/production-one-port/Dockerfile

@@ -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 - 0
docker-example/production-one-port/README.md

@@ -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.

+ 1 - 1
docker-example/simple-one-port/Caddyfile

@@ -11,4 +11,4 @@ root * /srv
 route {
 	try_files {path} {path}/ /404.html
 	file_server
-}
+}

+ 2 - 2
docker-example/simple-one-port/Dockerfile

@@ -4,7 +4,7 @@
 # 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.11
+FROM python:3.13
 
 # If the service expects a different port, provide it here (f.e Render expects port 10000)
 ARG PORT=8080
@@ -38,4 +38,4 @@ EXPOSE $PORT
 CMD [ -d alembic ] && reflex db migrate; \
     caddy start && \
     redis-server --daemonize yes && \
-    exec reflex run --env prod --backend-only
+    exec reflex run --env prod --backend-only

+ 1 - 1
docker-example/simple-two-port/Dockerfile

@@ -1,5 +1,5 @@
 # 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/*
 ENV REDIS_URL=redis://localhost PYTHONUNBUFFERED=1