add caddy-fastapi

Signed-off-by: Grant Birkinbine <grantbirki@github.com>
This commit is contained in:
Grant Birkinbine 2022-03-16 23:25:14 -07:00
parent 05095bbe0d
commit 2dfb65746b
No known key found for this signature in database
GPG Key ID: 9029BBCCC15C6F8C
10 changed files with 262 additions and 0 deletions

5
caddy-fastapi/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
__pycache__
# container volume files
data/*
!data/.gitkeep

147
caddy-fastapi/README.md Normal file
View File

@ -0,0 +1,147 @@
# Compose sample application
## Caddy/FastAPI application ⛳
Deploy [Caddy](https://caddyserver.com/) + [FastAPI](https://fastapi.tiangolo.com/) with docker-compose
Project structure:
```text
├── docker-compose.yaml
├── Dockerfile
├── requirements.txt
├── src
├── caddy
├── Caddyfile
├── Dockerfile
├── start.sh
├── caddy
├── Dockerfile
├── main.py
├── requirements.txt
```
[_docker-compose.yaml_](docker-compose.yaml)
```yaml
services:
fastapi:
container_name: fastapi
restart: unless-stopped
build:
context: ./src/fastapi
dockerfile: ./Dockerfile
ports:
- 8000:8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 5m
timeout: 5s
retries: 3
start_period: 15s
caddy:
container_name: caddy
restart: unless-stopped
build:
context: ./src/caddy
dockerfile: ./Dockerfile
ports:
- 80:80
- 443:443
volumes:
- ./data/caddy_data:/data
- ./data/caddy_config:/config
depends_on:
- fastapi
environment:
PROXY_BACKEND: fastapi
PROXY_PORT: 8000
DOMAIN: ${DOMAIN}
volumes:
caddy_data:
caddy_config:
```
## Deploy with docker-compose
```bash
docker-compose up --build
```
> Note: You will see `WARNING: The DOMAIN variable is not set. Defaulting to a blank string.` and that is expected - See the extra info section below for more details
## Expected result
Listing containers must show one container running and the port mapping as below:
```console
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
52d5fbe3dc5d caddy-fastapi_caddy "sh /app/start.sh" 12 seconds ago Up 1 second 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 2019/tcp caddy
09677bb1297e caddy-fastapi_fastapi "python -m uvicorn m…" 13 seconds ago Up 1 second (health: starting) 0.0.0.0:8000->8000/tcp
```
After the application starts, navigate to [localhost](https://localhost:443/) in your web browser and you should see the following json response:
```json
{"Hello":"World"}
```
Stop and remove the containers
```console
$ docker-compose down
WARNING: The DOMAIN variable is not set. Defaulting to a blank string.
Stopping caddy ... done
Stopping fastapi ... done
Removing caddy ... done
Removing fastapi ... done
Removing network caddy-fastapi_default
```
## Additional Information 📚
This section contains additional information about the docker-compose sample application
### TLS Certificate 🔐
Caddy automatically provisions TLS certificates for you. In order to make use of this awesome feature, do the following:
1. Ensure your server has ports `80` and `443` open
1. Have a DNS record pointed to your server for the domain you wish to obtain a certificate for (e.g. `app.example.org` -> `<IP address>`)
1. Export the env var for the domain you wish to use:
```bash
export DOMAIN=app.example.org
```
1. Start the docker-compose stack:
```bash
docker-compose up --build
```
1. Navigate to your domain and enjoy your easy TLS setup with Caddy! -> [https://app.example.org](https://app.example.orgg)
### Extra Extra Info 📚
Here is some extra info about the setup
#### Volumes 🛢️
The docker-compose file creates two volumes:
- `./data/caddy_data:/data`
- `./data/caddy_config:/config`
The config volume is used to mount Caddy configuration
The data volume is used to store certificate information. This is really important so that you are not re-requesting TLS certs each time you start your container. Doing so can cause you to hit Let's Encrypt rate limits that will prevent you from provisioning certificates.
### Environment Variables 📝
If you run the stack without the `DOMAIN` variable set in your environment, the stack will default to using `localhost`. This is ideal for testing out the stack locally.
If you set the `DOMAIN` variable, Caddy will attempt to provision a certificate for that domain. In order to do so, you will need DNS records pointed to that domain and you will need need traffic to access your server via port `80` and `443`.

View File

View File

@ -0,0 +1,42 @@
# To build the entire stack run 'make run'
version: '3.7'
services:
fastapi:
container_name: fastapi
restart: unless-stopped
build:
context: ./src/fastapi
dockerfile: ./Dockerfile
ports:
- 8000:8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 5m
timeout: 5s
retries: 3
start_period: 15s
caddy:
container_name: caddy
restart: unless-stopped
build:
context: ./src/caddy
dockerfile: ./Dockerfile
ports:
- 80:80
- 443:443
volumes:
- ./data/caddy_data:/data
- ./data/caddy_config:/config
depends_on:
- fastapi
environment:
PROXY_BACKEND: fastapi
PROXY_PORT: 8000
DOMAIN: ${DOMAIN}
volumes:
caddy_data:
caddy_config:

View File

@ -0,0 +1,5 @@
{$DOMAIN} {
reverse_proxy {$PROXY_BACKEND}:{$PROXY_PORT} {
header_down Strict-Transport-Security max-age=31536000;
}
}

View File

@ -0,0 +1,8 @@
FROM caddy/caddy:2.4.6-alpine
RUN mkdir /app
COPY start.sh /app/start.sh
COPY Caddyfile /etc/caddy/Caddyfile
CMD ["sh", "/app/start.sh"]

View File

@ -0,0 +1,12 @@
#!/bin/bash
set -e
if [ -z "$DOMAIN" ]
then
# If DOMAIN is blank, set to localhost
# Note: in prod, domain will be the actual domain
export DOMAIN="localhost"
fi
caddy run --config /etc/caddy/Caddyfile --adapter caddyfile

View File

@ -0,0 +1,27 @@
FROM python:alpine3.15
WORKDIR /app
# Install curl for healthchecks
RUN apk add curl
# Setup a nonroot user for security
RUN adduser -D nonroot
USER nonroot
# Upgrade pip
RUN pip install --upgrade pip
# Install dependencies
COPY requirements.txt .
RUN pip install --user --no-cache-dir --upgrade -r requirements.txt
# Copy the app
COPY main.py /app/main.py
# Expose the app's port
EXPOSE 8000
# Run the FastAPI server
ENTRYPOINT ["python", "-m"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@ -0,0 +1,14 @@
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/health")
def read_root():
return "OK"

View File

@ -0,0 +1,2 @@
fastapi==0.75.0
uvicorn==0.17.6