diff --git a/.dockerignore b/.dockerignore index f4796c2..e86a29e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,8 @@ .web -__pycache__/* \ No newline at end of file +.git +__pycache__/* +Dockerfile +Caddy.Dockerfile +compose.yaml +compose.*.yaml +uploaded_files \ No newline at end of file diff --git a/Caddyfile b/Caddyfile index 0963b26..49aa4cc 100644 --- a/Caddyfile +++ b/Caddyfile @@ -4,13 +4,13 @@ encode gzip @backend_routes path /_event/* /ping /_upload /_upload/* handle @backend_routes { - reverse_proxy app:8000 + reverse_proxy app:8000 } root * /srv route { - try_files {path} {path}/ /404.html - file_server + try_files {path} {path}/ /404.html + file_server } } diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index f3eb532..0000000 --- a/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -# Stage 1: init -FROM python:3.11 as init - -# 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 -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 -RUN pip install -r env/requirements.txt -r env/reflex_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 -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 [ -d alembic ] && reflex db migrate; reflex run --env prod - diff --git a/compose.prod.yaml b/compose.prod.yaml new file mode 100644 index 0000000..2255395 --- /dev/null +++ b/compose.prod.yaml @@ -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: \ No newline at end of file diff --git a/compose.tools.yaml b/compose.tools.yaml new file mode 100644 index 0000000..a48de0c --- /dev/null +++ b/compose.tools.yaml @@ -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: \ No newline at end of file diff --git a/compose.yml b/compose.yml index 5fea306..8c2f97b 100644 --- a/compose.yml +++ b/compose.yml @@ -1,19 +1,29 @@ +# 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 @@ -22,3 +32,11 @@ services: restart: always depends_on: - app + +volumes: + # SQLite data + db-data: + # Uploaded files + upload-data: + # TLS keys and certificates + caddy-data: \ No newline at end of file diff --git a/prod.Dockerfile b/prod.Dockerfile new file mode 100644 index 0000000..a526d8f --- /dev/null +++ b/prod.Dockerfile @@ -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 env/requirements.txt -r env/reflex_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 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..640718d --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +reflex==0.5.4