diff --git a/.railway/turl.toml b/.railway/turl.toml new file mode 100644 index 0000000..9c71dce --- /dev/null +++ b/.railway/turl.toml @@ -0,0 +1,15 @@ +[build] +builder = "DOCKERFILE" +dockerfilePath = "./Dockerfile" + +[deploy] +preDeployCommand = [ + "uv run --no-sync --no-dev alembic upgrade head" +] +startCommand = """ + sh -c ' + gunicorn -k uvicorn.workers.UvicornWorker source.app:app --bind 0.0.0.0:$PORT --workers $WORKERS + ' +""" + +healthcheckPath = "/status" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4612c0c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM python:3.13-slim + +ARG APP_DIR=/source +WORKDIR ${APP_DIR} + +ENV PATH=${APP_DIR}/.venv/bin:${PATH} \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 + +# Install gettext (for compiling messages to other languages) and uv (single static binary) and clean up +RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \ + && curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh \ + && apt-get purge -y curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy dependency files first for better layer caching +COPY pyproject.toml uv.lock ./ + +# Install dependencies +RUN uv sync --frozen --no-dev + +# Copy source code +COPY source/ ./source/ +COPY alembic.ini ./alembic.ini +COPY alembic/ ./alembic/ + +# Expose port (Railway will set PORT environment variable) +EXPOSE 8000 + +# Add health check +HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:${PORT:-8000}/status || exit 1 diff --git a/pyproject.toml b/pyproject.toml index 86793c7..8bd326a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ dependencies = [ "fastapi>=0.116.1", "fastapi-throttle>=0.1.8", "greenlet>=3.2.4", + "gunicorn>=23.0.0", "httpx>=0.28.1", "pydantic>=2.11.7", "pydantic-settings>=2.10.1", diff --git a/source/app.py b/source/app.py index 0f15701..efdbce5 100644 --- a/source/app.py +++ b/source/app.py @@ -12,7 +12,7 @@ def create_app() -> FastAPI: application = FastAPI( name="tURL", description="Tiny microservice for shortening URLs.", - docs_url="/swagger", + docs_url=None, redoc_url="/docs", dependencies=[ Depends(RateLimiter(times=settings.rate_limit_requests, seconds=settings.rate_limit_window_seconds)), diff --git a/uv.lock b/uv.lock index d8e02b2..6ae64a1 100644 --- a/uv.lock +++ b/uv.lock @@ -253,6 +253,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, ] +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -709,6 +721,7 @@ dependencies = [ { name = "fastapi" }, { name = "fastapi-throttle" }, { name = "greenlet" }, + { name = "gunicorn" }, { name = "httpx" }, { name = "pydantic" }, { name = "pydantic-settings" }, @@ -737,6 +750,7 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.116.1" }, { name = "fastapi-throttle", specifier = ">=0.1.8" }, { name = "greenlet", specifier = ">=3.2.4" }, + { name = "gunicorn", specifier = ">=23.0.0" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "pydantic", specifier = ">=2.11.7" }, { name = "pydantic-settings", specifier = ">=2.10.1" },