Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
FROM python:3.12-slim-bookworm

WORKDIR /app
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# copy/create bare minimum files needed to install dependencies
COPY pyproject.toml README.md uv.lock /app/
RUN mkdir -p /app/src/files_api/
RUN touch /app/src/files_api/__init__.py
WORKDIR /app

# Set environment variables for uv to use the system Python environment
ENV UV_SYSTEM_PYTHON=true
ENV VIRTUAL_ENV=/usr/local/
ENV PATH="/usr/local/bin:$PATH"
ENV UV_LINK_MODE=copy UV_COMPILE_BYTECODE=1
ENV UV_SYSTEM_PYTHON=false
ENV UV_PROJECT_ENVIRONMENT=/app/.venv
# ^^^https://docs.astral.sh/uv/guides/integration/docker/#optimizations
# ^^^https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode
# ^^^https://docs.astral.sh/uv/guides/integration/docker/#caching

# install dependencies from pyproject.toml
RUN pip install --upgrade pip uv
RUN uv sync --no-cache --group=docker --frozen --active --project=/app/
# RUN source /app/.venv/bin/activate
# RUN uv pip install --no-cache --group=docker --editable "/app/"
# RUN pip install --editable "/app/[docker]"
# ENV UV_NO_CACHE=1
# If you're not mounting the cache, image size can be reduced by using the --no-cache flag or setting UV_NO_CACHE.

# Install dependencies without installing the project itself
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project --group docker

# copy the rest of the source code
# Copy project files
COPY pyproject.toml README.md uv.lock /app/
COPY ./src/ /app/src/
COPY ./tests/mocks /app/tests/mocks

# Sync the project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --group docker

ENV VIRTUAL_ENV=/app/.venv
ENV PATH="/app/.venv/bin:$PATH"

# create the s3 bucket if desired(if false then using real S3 bucket), then start the fastapi app
CMD (\
if [ "$CREATE_BUCKET_ON_STARTUP" = "true" ]; then \
uv run --active -- python -c "import boto3; boto3.client('s3').create_bucket(Bucket='${S3_BUCKET_NAME}')"; \
uv run -- python -c "import boto3; boto3.client('s3').create_bucket(Bucket='${S3_BUCKET_NAME}')"; \
fi \
) \
&& uv run --active -- uvicorn files_api.main:create_app --factory --host 0.0.0.0 --port 8000 --reload
&& uv run -- uvicorn files_api.main:create_app --factory --host 0.0.0.0 --port 8000 --reload

# """
# Ref:
# - https://github.com/astral-sh/uv-docker-example/tree/main
# - https://docs.astral.sh/uv/guides/integration/docker/#getting-started
# - https://docs.astral.sh/uv/guides/integration/fastapi/#migrating-an-existing-fastapi-project
# - https://docs.astral.sh/uv/guides/integration/aws-lambda/#using-uv-with-aws-lambda
# - https://docs.astral.sh/uv/concepts/projects/config/#project-environment-path
# """
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ This project is a more polished version of the [cloud-engineering-project](https
- [x] Added Secret Manager to store OpenAI API key securely.
- [x] Used AWS SSM Parameter Store to store the OpenAI API key instead of Secrets Manager. [ref](docs/Secrets-Manager-and-SSM-Parameter-Store.md)
- This is free to use and has no additional cost unlike Secrets Manager $0.40 per secret per month.
- [ ] Setup the Dockerfile with the recommended way of using [uv in Docker](https://docs.astral.sh/uv/guides/integration/docker/).
- [ ] CDK rebuilds the Lambda Layer Docker image on every deployment. Is it possible to cache it locally and only rebuild when there are changes to files like `pyproject.toml` or `uv.lock`?
- [x] Setup the Dockerfile with the recommended way of using [uv in Docker](https://docs.astral.sh/uv/guides/integration/docker/).
- [x] CDK rebuilds the Lambda Layer Docker image on every deployment. Is it possible to cache it locally and only rebuild when there are changes to files like `pyproject.toml` or `uv.lock`?
- [ ] Try Docker multi-stage builds and configure [watch](https://docs.astral.sh/uv/guides/integration/docker/#configuring-watch-with-docker-compose) with docker compose.
- [ ] Implement API versioning strategy (like v1 in the path).
- [ ] Setup CI/CD pipeline to deploy the API to AWS using GitHub Actions.
- [ ] Deployment Stratgies like Blue-Green, Canary deployments, etc.
Expand Down
11 changes: 6 additions & 5 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# ship the fastapi metrics, traces, and logs to AWS

services:

fastapi:
# logs to stdout (including metrics in EMF json format); pushes xray traces to the xray-daemon
build:
Expand All @@ -11,7 +10,7 @@ services:
- "8000:8000"
environment:
# app config
CREATE_BUCKET_ON_STARTUP: "true" # in Dockerfile
CREATE_BUCKET_ON_STARTUP: "true" # in Dockerfile
S3_BUCKET_NAME: "mock-bucket"
LOGURU_LEVEL: INFO
# openai mock (comment these out if you want to use real openai)
Expand Down Expand Up @@ -43,7 +42,8 @@ services:
# - .env
# - openai.env # create this file if you want to use real OpenAI
volumes:
- ./:/app
- ./:/app # Mount local directory
- /app/.venv # Anonymous volume: preserve this directory's contents in container

aws-mock:
# mock aws' endpoints on port 5000 (with moto)
Expand All @@ -58,13 +58,14 @@ services:
image: openai-mock
build:
dockerfile: Dockerfile
entrypoint: python ./tests/mocks/openai_fastapi_mock_app.py
entrypoint: ["uv", "run", "./tests/mocks/openai_fastapi_mock_app.py"]
environment:
OPENAI_MOCK_PORT: "1080"
ports:
- "1080:1080"
volumes:
- ./:/app
- ./:/app # Mount local directory
- /app/.venv # Anonymous volume: preserve this directory's contents in container

logspout:
# Logs: uses the docker daemon to collect logs from fastapi's stdout and push to cloudwatch
Expand Down
46 changes: 46 additions & 0 deletions docs/CDK-Asset-Hash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
In the AWS CDK, the `asset_hash` is a property used to determine if local assets (like your AWS Lambda function's source code directory) have changed, which is crucial for efficient deployments.

**How AWS CDK Uses Asset Hashes**

When you define a Lambda function using `lambda.Code.fromAsset(path)` or similar methods, the CDK performs the following steps during synthesis and deployment:

1. **Calculate Hash:** The CDK calculates a hash of the local asset (e.g., the contents of your Lambda function's directory).
2. **Generate Cloud Assembly:** This hash is included in the cloud assembly (the `cdk.out` directory by default) metadata.
3. **Deployment Check:** During deployment (`cdk deploy`), the CLI uses this hash to check if the asset already exists in the S3 bucket or ECR repository created during the CDK bootstrapping process.
4. **Optimization:** If the hash matches an existing asset, the CDK skips re-uploading the content, optimizing the deployment time. If the hash is different, it means the content has changed, and the new asset is uploaded.

**Customizing the Asset Hash**

By default, the CDK automatically calculates a hash based on the source code content (`AssetHashType.SOURCE`). However, you can manually control the hashing behavior using the `assetHash` and `assetHashType` properties within the asset options.

This can be useful if the automatic hashing is non-deterministic (e.g., due to temporary files in a build process) or if you want to force an update.

You can use the `assetHashType` property in the `AssetOptions` to specify how the hash should be calculated:

- **`AssetHashType.SOURCE` (Default):** The hash is calculated based on the contents of the source directory or file.
- **`AssetHashType.BUNDLE`:** The hash is calculated on the output of a bundling command (useful when using asset bundling with Docker).
- **`AssetHashType.CUSTOM`:** Allows you to provide a specific, custom hash string using the `assetHash` property.

**Example (Python)**

When creating a Lambda function in Python, you can specify custom asset options:

```python
import aws_cdk as cdk
from aws_cdk import aws_lambda as lambda_

# ... inside your stack definition

my_lambda_function = lambda_.Function(self, "MyLambdaFunction",
runtime=lambda_.Runtime.PYTHON_3_11,
handler="index.handler",
code=lambda_.Code.from_asset("path/to/your/lambda/code",
asset_hash_type=cdk.AssetHashType.CUSTOM, # Set hash type to CUSTOM
asset_hash="my-specific-hash-v1" # Provide a custom hash string
)
)
```

**Important:** If you use `AssetHashType.CUSTOM`, you are responsible for updating the hash string every time the asset content changes; otherwise, deployments might not invalidate and upload the new code.

ref: https://docs.aws.amazon.com/cdk/v2/guide/assets.html
Loading