From 95b28fd02a703141dc4e7674cb3732585775ef33 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 14 Aug 2023 18:15:06 -0700 Subject: [PATCH] update Dockerfile example (#1520) --- docker-example/.dockerignore | 2 + docker-example/Caddy.Dockerfile | 4 ++ docker-example/Caddyfile | 18 +++++++++ docker-example/Dockerfile | 59 ++++++++++++------------------ docker-example/README.md | 65 ++++++++++++++++++++++++--------- docker-example/compose.yaml | 21 +++++++++++ 6 files changed, 116 insertions(+), 53 deletions(-) create mode 100644 docker-example/.dockerignore create mode 100644 docker-example/Caddy.Dockerfile create mode 100644 docker-example/Caddyfile create mode 100644 docker-example/compose.yaml diff --git a/docker-example/.dockerignore b/docker-example/.dockerignore new file mode 100644 index 000000000..f4796c2eb --- /dev/null +++ b/docker-example/.dockerignore @@ -0,0 +1,2 @@ +.web +__pycache__/* \ No newline at end of file diff --git a/docker-example/Caddy.Dockerfile b/docker-example/Caddy.Dockerfile new file mode 100644 index 000000000..557d22fe4 --- /dev/null +++ b/docker-example/Caddy.Dockerfile @@ -0,0 +1,4 @@ +FROM library/caddy + +COPY --from=local/reflex-app /app/.web/_static /srv +ADD Caddyfile /etc/caddy/Caddyfile \ No newline at end of file diff --git a/docker-example/Caddyfile b/docker-example/Caddyfile new file mode 100644 index 000000000..8945e7951 --- /dev/null +++ b/docker-example/Caddyfile @@ -0,0 +1,18 @@ +{$DOMAIN} + +encode gzip + +@backend_routes path /event/* /upload /ping +handle @backend_routes { + reverse_proxy app:8000 +} + +route { + try_files {path} {path}.html + file_server { + root /srv + pass_thru + } + # proxy dynamic routes to nextjs server + reverse_proxy app:3000 +} diff --git a/docker-example/Dockerfile b/docker-example/Dockerfile index 30e003ae8..11fde7b99 100644 --- a/docker-example/Dockerfile +++ b/docker-example/Dockerfile @@ -1,49 +1,38 @@ -FROM python:3.11-slim as base +# Stage 1: init +FROM python:3.11 as init -RUN adduser --disabled-password reflex - - -FROM base as build +# Pass `--build-arg API_URL=http://app.example.com:8000` during build +ARG API_URL +# Copy local context to `/app` inside container (see .dockerignore) WORKDIR /app -ENV VIRTUAL_ENV=/app/venv -RUN python3 -m venv $VIRTUAL_ENV -ENV PATH="$VIRTUAL_ENV/bin:$PATH" - COPY . . -RUN pip install wheel \ - && pip install -r requirements.txt +# Reflex will install bun, nvm, and node to `$HOME/.reflex` (/app/.reflex) +ENV HOME=/app +# Create virtualenv which will be copied into final container +ENV VIRTUAL_ENV=/app/.venv +ENV PATH="$VIRTUAL_ENV/bin:$PATH" +RUN python3 -m venv $VIRTUAL_ENV -FROM base as runtime +# Install app requirements and reflex inside virtualenv +RUN pip install -r requirements.txt -RUN apt-get update && apt-get install -y \ - curl \ - && curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \ - && apt-get update && apt-get install -y \ - nodejs \ - unzip \ - && rm -rf /var/lib/apt/lists/* - -ENV PATH="/app/venv/bin:$PATH" - - -FROM runtime as init - -WORKDIR /app -ENV BUN_INSTALL="/app/.bun" -COPY --from=build /app/ /app/ +# Deploy templates and prepare app RUN reflex init +# Export static copy of frontend to /app/.web/_static (and pre-install frontend packages) +RUN reflex export --frontend-only --no-zip -FROM runtime -COPY --chown=reflex --from=init /app/ /app/ -USER reflex +# 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", "run" , "--env", "prod"] - -EXPOSE 3000 -EXPOSE 8000 +CMD reflex db migrate && reflex run --env prod diff --git a/docker-example/README.md b/docker-example/README.md index b67ab365b..e377b7f9c 100644 --- a/docker-example/README.md +++ b/docker-example/README.md @@ -1,37 +1,66 @@ -# Reflex Container Image Build +# Reflex Docker Container This example describes how to create and use a container image for Reflex with your own code. ## Update Requirements -The `requirements.txt` includes the reflex package which is need to install Reflex framework. If you use additional packages in your project you have add this in the `requirements.txt` first. Copy the `Dockerfile` and the `requirements.txt` file in your project folder. - -## Customize Reflex Config - -The `rxconfig.py` includes the configuration of your Reflex service. Edit the file like the following configuration. If you want to use a custom database you can set the endpoint in this file. Ensure that `api_url` points to the publicly accessible hostname where the container will be running. - -```python -import reflex as rx - -config = rx.Config( - app_name="app", - api_url="http://app.example.com:8000", - db_url="sqlite:///reflex.db", -) -``` +The `requirements.txt` includes the reflex package which is needed to install +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 To build your container image run the following command: ```bash -docker build -t reflex-project:latest . +docker build -t reflex-app:latest . --build-arg API_URL=http://app.example.com:8000 ``` +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 -d -p 3000:3000 -p 8000:8000 --name app reflex-project:latest +docker run -p 3000:3000 -p 8000:8000 --name app reflex-app:latest ``` + +It may take a few seconds for the service to become available. + +# 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. + +## Customize `Caddyfile` + +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) + +```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. + +## Run Reflex Production Service + +```bash +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. diff --git a/docker-example/compose.yaml b/docker-example/compose.yaml new file mode 100644 index 000000000..4d892907f --- /dev/null +++ b/docker-example/compose.yaml @@ -0,0 +1,21 @@ +# During build and run, set environment DOMAIN pointing +# to publicly accessible domain where app will be hosted +services: + app: + image: local/reflex-app + build: + context: . + args: + API_URL: https://${DOMAIN:-localhost} + + webserver: + environment: + DOMAIN: ${DOMAIN:-localhost} + ports: + - 443:443 + - 80:80 # for acme-challenge via HTTP + build: + context: . + dockerfile: Caddy.Dockerfile + depends_on: + - app \ No newline at end of file