tina4 3.8.69

Tina4 — Unified CLI for Python, PHP, Ruby, and Node.js frameworks
# Tina4 Python — ASGI production image. Generated by
# `tina4 deploy docker --runtime asgi`.
#
# WHEN TO PICK THIS. The default image runs `tina4 serve --production`, which is
# one event loop in one process. This one hands the application to a real ASGI
# server so you get ITS process model: uvicorn workers, hypercorn's HTTP/2 and
# HTTP/3, or granian's Rust loop. Reach for it when one loop is no longer
# enough, or when your platform already standardises on an ASGI server.
#
# Tina4 exposes a plain ASGI 3 callable, so any compliant server works. The
# three below are pre-installed; swap TINA4_ASGI_SERVER for whichever you run.
#
# THE BOOTSTRAP MATTERS. asgi.py calls tina4_python's asgi(), which discovers
# src/routes BEFORE handing the callable over. Pointing a server straight at
# `tina4_python.core.server:app` starts a working application with NO ROUTES IN
# IT - measured: /hello answers 404 through the bare app and 200 through
# asgi(). That failure is silent, so the entry point is generated rather than
# left for you to discover.
ARG TINA4_CLI_IMAGE=ghcr.io/tina4stack/tina4-cli:v3.8.63
FROM ${TINA4_CLI_IMAGE} AS tina4cli

FROM python:3.12-slim AS deps
WORKDIR /app
RUN pip install --no-cache-dir uv
COPY pyproject.toml uv.lock* ./
# All three servers at BUILD time, so switching TINA4_ASGI_SERVER never means a
# network install at container start - which fails outright in an air-gapped
# deploy and slows every boot elsewhere.
RUN uv sync --frozen --no-dev \
 && uv pip install uvicorn[standard] hypercorn granian

FROM python:3.12-slim
WORKDIR /app
COPY --from=tina4cli /usr/local/bin/tina4 /usr/local/bin/tina4
ENV PATH="/app/.venv/bin:$PATH" \
    TINA4_OVERRIDE_CLIENT=true \
    TINA4_DEBUG=false \
    TINA4_ASGI_SERVER=uvicorn \
    TINA4_ASGI_WORKERS=4 \
    TINA4_ASGI_HOST=0.0.0.0 \
    TINA4_ASGI_PORT=7145
COPY --from=deps /app/.venv /app/.venv
COPY . /app
COPY asgi.py /app/asgi.py

# Fail the BUILD if the bootstrap cannot even be imported. A broken asgi.py
# otherwise surfaces as a container that exits on first deploy.
RUN python -c "import ast, sys; ast.parse(open('/app/asgi.py').read())" \
 && python -c "import uvicorn, hypercorn, granian" \
 || { echo 'ASGI bootstrap or servers missing'; exit 1; }

EXPOSE 7145

COPY docker-entrypoint.asgi.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
CMD ["/usr/local/bin/docker-entrypoint.sh"]