|
| 1 | +# FastAPI Serverless Project Setup Guide |
| 2 | +*A complete guide to CI/CD repository configuration before writing application code* |
| 3 | + |
| 4 | +## Why This Matters |
| 5 | +Setting up proper infrastructure before writing application code is crucial for long-term project success. Each component in this guide addresses specific challenges that become exponentially harder to implement as the codebase grows. By establishing these foundations early, we create a robust development environment that supports quality, testing, and automated deployments from day one. |
| 6 | + |
| 7 | +### Key Benefits |
| 8 | +- Consistent development environment across team members |
| 9 | +- Automated quality checks and deployments from the start |
| 10 | +- Clear separation of development and production environments |
| 11 | +- Built-in testing practices that scale with the code |
| 12 | +- Infrastructure-as-code approach for reproducible deployments |
| 13 | + |
| 14 | +## The CI/CD Pipeline |
| 15 | +On the topic of good engineering and quality code, this is the pipeline and setup that we should have on each new repo before adding application code. |
| 16 | + |
| 17 | +This v1.0.0 approach ensures quality, maintainability, and proper CI/CD from day one. |
| 18 | + |
| 19 | +- 1️⃣ Git repo |
| 20 | +- 2️⃣ FastAPI base app |
| 21 | +- 3️⃣ CI pipeline (dev and main) |
| 22 | +- 4️⃣ Unit, integration and QA tests |
| 23 | +- 5️⃣ 100% code coverage |
| 24 | +- 6️⃣ Auto-tagging on commits |
| 25 | +- 7️⃣ Create Docker container |
| 26 | +- 8️⃣ Push Docker container to AWS ECR |
| 27 | +- 9️⃣ Create AWS Lambda |
| 28 | +- 🔟 Enable AWS Function URL |
| 29 | +- 1️⃣1️⃣ Ensure AWS Lambda/FastAPI works |
| 30 | + |
| 31 | +Adding these elements later when complexity has grown becomes exponentially more difficult. This guide provides step-by-step instructions to implement this foundation correctly from the start. |
| 32 | + |
| 33 | +## 1. Repository Setup |
| 34 | +Repository setup establishes the foundation for the entire development workflow. A well-structured repository ensures consistent development practices, clear code organization, and efficient collaboration. The directory structure follows separation of concerns principles and supports independent testing of each component. |
| 35 | + |
| 36 | +### Initial Structure |
| 37 | +```bash |
| 38 | +mkdir my-fastapi-project |
| 39 | +cd my-fastapi-project |
| 40 | +git init |
| 41 | + |
| 42 | +# Create core directories |
| 43 | +mkdir -p {src,tests/{unit,integration,qa},deploy/{docker,lambdas},.github/workflows} |
| 44 | + |
| 45 | +# Create essential files |
| 46 | +touch README.md LICENSE pyproject.toml requirements-test.txt |
| 47 | +touch .gitignore .env.example |
| 48 | +``` |
| 49 | + |
| 50 | +### Base Configuration Files |
| 51 | +Configuration files establish project dependencies, testing requirements, and build settings. Poetry provides reliable dependency management while maintaining reproducible environments. The test requirements ensure consistent quality checks across all environments. |
| 52 | + |
| 53 | +**pyproject.toml** |
| 54 | +```toml |
| 55 | +[tool.poetry] |
| 56 | +name = "my_fastapi_project" |
| 57 | +version = "v0.0.1" |
| 58 | +description = "FastAPI Serverless Project" |
| 59 | +authors = ["Your Name <your.email@domain.com>"] |
| 60 | +license = "Apache 2.0" |
| 61 | +readme = "README.md" |
| 62 | + |
| 63 | +[tool.poetry.dependencies] |
| 64 | +python = "^3.11" |
| 65 | +fastapi = "*" |
| 66 | +mangum = "*" |
| 67 | +httpx = "*" |
| 68 | +uvicorn = "*" |
| 69 | + |
| 70 | +[build-system] |
| 71 | +requires = ["poetry-core>=1.0.0"] |
| 72 | +build-backend = "poetry.core.masonry.api" |
| 73 | +``` |
| 74 | + |
| 75 | +**requirements-test.txt** |
| 76 | +```text |
| 77 | +pytest |
| 78 | +pytest-cov |
| 79 | +coveralls |
| 80 | +``` |
| 81 | + |
| 82 | +## 2. FastAPI Base Application |
| 83 | +FastAPI applications require clear structure to support scalability and maintenance. This setup separates routes, utilities, and core application logic while maintaining AWS Lambda compatibility through Mangum. The structure supports independent testing of components and clear separation of concerns. |
| 84 | + |
| 85 | +### Core Application Structure |
| 86 | +``` |
| 87 | +src/ |
| 88 | +├── app/ |
| 89 | +│ ├── __init__.py |
| 90 | +│ ├── main.py |
| 91 | +│ ├── routes/ |
| 92 | +│ │ ├── __init__.py |
| 93 | +│ │ └── info.py |
| 94 | +│ └── utils/ |
| 95 | +│ ├── __init__.py |
| 96 | +│ └── version.py |
| 97 | +└── handler.py |
| 98 | +``` |
| 99 | + |
| 100 | +### Basic FastAPI Application (main.py) |
| 101 | +The main application file serves as the entry point, combining route configuration with AWS Lambda integration. Mangum handles the translation between AWS Lambda events and FastAPI requests, ensuring seamless serverless operation while maintaining local development capabilities. |
| 102 | + |
| 103 | +```python |
| 104 | +from fastapi import FastAPI |
| 105 | +from mangum import Mangum |
| 106 | +from app.routes import info |
| 107 | + |
| 108 | +app = FastAPI() |
| 109 | +app.include_router(info.router, prefix="/info", tags=["info"]) |
| 110 | + |
| 111 | +# Lambda handler |
| 112 | +handler = Mangum(app) |
| 113 | + |
| 114 | +if __name__ == "__main__": |
| 115 | + import uvicorn |
| 116 | + uvicorn.run(app, host="0.0.0.0", port=8000) |
| 117 | +``` |
| 118 | + |
| 119 | +### Info Routes (routes/info.py) |
| 120 | +Route modules provide clear API endpoint organization. The info routes offer essential system health checks and version information, crucial for monitoring and deployment verification. This structure supports easy addition of new endpoints while maintaining code organization. |
| 121 | + |
| 122 | +```python |
| 123 | +from fastapi import APIRouter |
| 124 | +from app.utils.version import get_version |
| 125 | + |
| 126 | +router = APIRouter() |
| 127 | + |
| 128 | +@router.get("/version") |
| 129 | +async def version(): |
| 130 | + return {"version": get_version()} |
| 131 | + |
| 132 | +@router.get("/ping") |
| 133 | +async def ping(): |
| 134 | + return "pong" |
| 135 | +``` |
| 136 | + |
| 137 | +## 3. CI Pipeline Setup |
| 138 | +Continuous Integration ensures code quality and automated deployments. The dual pipeline approach (dev/main) enables feature development while maintaining stable production code. Automated versioning and testing prevent broken deployments and maintain consistent release practices. |
| 139 | + |
| 140 | +### GitHub Actions Workflows |
| 141 | + |
| 142 | +**.github/workflows/ci-pipeline-dev.yml** |
| 143 | +```yaml |
| 144 | +name: CI Pipeline - DEV |
| 145 | +on: |
| 146 | + push: |
| 147 | + branches: |
| 148 | + - dev |
| 149 | + |
| 150 | +env: |
| 151 | + RELEASE_TYPE: 'minor' |
| 152 | + |
| 153 | +jobs: |
| 154 | + run-tests: |
| 155 | + name: "Run tests" |
| 156 | + runs-on: ubuntu-latest |
| 157 | + steps: |
| 158 | + - uses: actions/checkout@v4 |
| 159 | + - name: Install dependencies |
| 160 | + run: | |
| 161 | + python -m pip install --upgrade pip |
| 162 | + pip install -r requirements-test.txt |
| 163 | + - name: Run tests |
| 164 | + run: pytest tests/ --cov=src/ |
| 165 | + |
| 166 | + increment-tag: |
| 167 | + needs: run-tests |
| 168 | + runs-on: ubuntu-latest |
| 169 | + steps: |
| 170 | + - uses: actions/checkout@v4 |
| 171 | + - name: Increment version |
| 172 | + run: | |
| 173 | + # Version increment logic here |
| 174 | + git tag v0.1.0 |
| 175 | + git push origin --tags |
| 176 | +``` |
| 177 | +
|
| 178 | +**.github/workflows/ci-pipeline-main.yml** |
| 179 | +```yaml |
| 180 | +name: CI Pipeline - MAIN |
| 181 | +on: |
| 182 | + push: |
| 183 | + branches: |
| 184 | + - main |
| 185 | + |
| 186 | +env: |
| 187 | + RELEASE_TYPE: 'major' |
| 188 | + |
| 189 | +jobs: |
| 190 | + # Similar to dev pipeline but with additional deployment steps |
| 191 | + deploy-to-aws: |
| 192 | + needs: [run-tests, increment-tag] |
| 193 | + runs-on: ubuntu-latest |
| 194 | + steps: |
| 195 | + - name: Configure AWS credentials |
| 196 | + uses: aws-actions/configure-aws-credentials@v1 |
| 197 | + with: |
| 198 | + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} |
| 199 | + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} |
| 200 | + aws-region: ${{ secrets.AWS_REGION }} |
| 201 | +``` |
| 202 | +
|
| 203 | +## 4. Testing Framework |
| 204 | +Multi-layer testing catches issues at different stages of development. Unit tests verify component behavior, integration tests ensure proper system interaction, and QA tests validate production deployment. 100% coverage ensures no untested code paths exist. |
| 205 | +
|
| 206 | +### Unit Tests (tests/unit/test_version.py) |
| 207 | +```python |
| 208 | +from unittest import TestCase |
| 209 | +from app.utils.version import get_version |
| 210 | + |
| 211 | +class TestVersion(TestCase): |
| 212 | + def test_get_version(self): |
| 213 | + version = get_version() |
| 214 | + assert isinstance(version, str) |
| 215 | + assert version.startswith('v') |
| 216 | +``` |
| 217 | +
|
| 218 | +### Integration Tests (tests/integration/test_routes.py) |
| 219 | +Integration tests validate the complete request-response cycle using FastAPI's test client. These tests ensure proper routing, middleware functionality, and response formatting. They catch issues that unit tests might miss while verifying the entire API behavior. |
| 220 | +
|
| 221 | +```python |
| 222 | +from fastapi.testclient import TestClient |
| 223 | +from app.main import app |
| 224 | + |
| 225 | +client = TestClient(app) |
| 226 | + |
| 227 | +def test_version_endpoint(): |
| 228 | + response = client.get("/info/version") |
| 229 | + assert response.status_code == 200 |
| 230 | + assert "version" in response.json() |
| 231 | + |
| 232 | +def test_ping_endpoint(): |
| 233 | + response = client.get("/info/ping") |
| 234 | + assert response.status_code == 200 |
| 235 | + assert response.json() == "pong" |
| 236 | +``` |
| 237 | +
|
| 238 | +### QA Tests (tests/qa/test_lambda.py) |
| 239 | +QA tests validate the deployed Lambda function through its public URL. These tests verify the complete deployment pipeline, including AWS configuration, networking, and Lambda execution environment. They serve as the final quality gate before production traffic. |
| 240 | +
|
| 241 | +```python |
| 242 | +import pytest |
| 243 | +import requests |
| 244 | +from unittest import TestCase |
| 245 | + |
| 246 | +class TestLambdaEndpoints(TestCase): |
| 247 | + @classmethod |
| 248 | + def setUpClass(cls): |
| 249 | + cls.lambda_url = "your-lambda-url" |
| 250 | + |
| 251 | + def test_version_endpoint_live(self): |
| 252 | + response = requests.get(f"{self.lambda_url}/info/version") |
| 253 | + assert response.status_code == 200 |
| 254 | + assert "version" in response.json() |
| 255 | +``` |
| 256 | +
|
| 257 | +## 5. Docker Configuration |
| 258 | +Docker containers ensure consistent environments across development and production. The configuration includes AWS Lambda compatibility layers and optimized Python settings. Multi-stage builds and proper base images maintain small deployment sizes while supporting development needs. |
| 259 | +
|
| 260 | +### Dockerfile |
| 261 | +```dockerfile |
| 262 | +FROM python:3.11-slim |
| 263 | + |
| 264 | +RUN pip install mangum uvicorn fastapi |
| 265 | + |
| 266 | +COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 \ |
| 267 | + /lambda-adapter /opt/extensions/lambda-adapter |
| 268 | + |
| 269 | +WORKDIR /app |
| 270 | +COPY ./src /app/src |
| 271 | +ENV PYTHONPATH="/app" |
| 272 | +ENV PORT=8080 |
| 273 | + |
| 274 | +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080"] |
| 275 | +``` |
| 276 | + |
| 277 | +### docker-compose.yml |
| 278 | +Docker Compose facilitates local development by matching production container configurations. It provides volume mounting for live code updates and port mapping for local access. This setup ensures development closely matches production behavior while maintaining developer productivity. |
| 279 | + |
| 280 | +```yaml |
| 281 | +services: |
| 282 | + app: |
| 283 | + build: |
| 284 | + context: . |
| 285 | + dockerfile: deploy/docker/Dockerfile |
| 286 | + ports: |
| 287 | + - "8080:8080" |
| 288 | + volumes: |
| 289 | + - ./src:/app/src |
| 290 | +``` |
| 291 | +
|
| 292 | +## 6. AWS Lambda Deployment |
| 293 | +Serverless deployment reduces operational overhead and provides automatic scaling. The Lambda setup includes proper memory allocation, timeout configurations, and URL endpoints. Infrastructure-as-code ensures reproducible deployments and proper version tracking. |
| 294 | +
|
| 295 | +### Lambda Function Setup |
| 296 | +```python |
| 297 | +from typing import Dict |
| 298 | +from aws_cdk import ( |
| 299 | + aws_lambda as _lambda, |
| 300 | + aws_ecr as ecr, |
| 301 | + core |
| 302 | +) |
| 303 | + |
| 304 | +class LambdaStack(core.Stack): |
| 305 | + def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: |
| 306 | + super().__init__(scope, id, **kwargs) |
| 307 | + |
| 308 | + # Create Lambda function |
| 309 | + function = _lambda.DockerImageFunction( |
| 310 | + self, 'FastAPIFunction', |
| 311 | + code=_lambda.DockerImageCode.from_ecr( |
| 312 | + repository=ecr.Repository.from_repository_name( |
| 313 | + self, 'ECRRepo', |
| 314 | + repository_name='your-repo-name' |
| 315 | + ) |
| 316 | + ), |
| 317 | + memory_size=1024, |
| 318 | + timeout=core.Duration.seconds(30), |
| 319 | + ) |
| 320 | + |
| 321 | + # Add function URL |
| 322 | + function.add_function_url( |
| 323 | + auth_type=_lambda.FunctionUrlAuthType.NONE |
| 324 | + ) |
| 325 | +``` |
| 326 | + |
| 327 | +## 7. Validation Steps |
| 328 | +Comprehensive validation prevents deployment issues and ensures system health. The steps cover local development, container verification, and production deployment checks. Automated validation in CI/CD pipelines prevents manual errors and maintains deployment quality. |
| 329 | + |
| 330 | +### Local Testing |
| 331 | +```bash |
| 332 | +# Start local server |
| 333 | +uvicorn src.main:app --reload |
| 334 | + |
| 335 | +# Run test suite |
| 336 | +pytest tests/ --cov=src/ |
| 337 | + |
| 338 | +# Build and run Docker container |
| 339 | +docker-compose up --build |
| 340 | +``` |
| 341 | + |
| 342 | +### AWS Deployment Validation |
| 343 | +Deployment validation ensures the entire pipeline functions correctly. These commands verify ECR push permissions, container registry access, and Lambda URL functionality. Regular validation prevents deployment-related downtimes and ensures system reliability. |
| 344 | + |
| 345 | +```bash |
| 346 | +# Push to ECR |
| 347 | +aws ecr get-login-password --region region | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com |
| 348 | +docker push aws_account_id.dkr.ecr.region.amazonaws.com/your-repo:latest |
| 349 | + |
| 350 | +# Test Lambda URL |
| 351 | +curl https://your-lambda-url/info/version |
| 352 | +``` |
| 353 | + |
| 354 | +## Best Practices |
| 355 | +These practices form the foundation of professional development workflows. Each category addresses specific challenges in modern software development, ensuring code quality, team collaboration, and system reliability. |
| 356 | + |
| 357 | +1. **Version Control** |
| 358 | + - Use semantic versioning |
| 359 | + - Maintain clean git history |
| 360 | + - Protect main branch |
| 361 | + - Require PR reviews |
| 362 | + |
| 363 | +2. **Testing** |
| 364 | + - Maintain 100% code coverage |
| 365 | + - Implement all test types from start |
| 366 | + - Use fixtures for test data |
| 367 | + - Mock external services |
| 368 | + |
| 369 | +3. **CI/CD** |
| 370 | + - Automate all deployments |
| 371 | + - Include security scans |
| 372 | + - Test in multiple Python versions |
| 373 | + - Maintain deployment history |
| 374 | + |
| 375 | +4. **Documentation** |
| 376 | + - Keep README updated |
| 377 | + - Document all endpoints |
| 378 | + - Include setup instructions |
| 379 | + - Document deployment process |
| 380 | + |
| 381 | +Remember: This foundation guarantees maintainable, scalable applications. Invest time in setting it up correctly before writing application code. |
0 commit comments