- HTML 39.4%
- Python 27.6%
- CSS 23.1%
- JavaScript 9.3%
- Dockerfile 0.6%
| .scratch/conversion-flow-seo | ||
| backend | ||
| docs | ||
| .env.example | ||
| .gitignore | ||
| Caddyfile | ||
| CLAUDE.md | ||
| CONTEXT.md | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| LICENSE | ||
| README.md | ||
| ruff.toml | ||
Avian Technologies Group — marketing site
A single-page marketing website built with FastAPI + Jinja2, served behind a Caddy reverse proxy via Docker Compose. It renders the home page and handles a contact form that emails submissions over SMTP. No database, no login, no persistent state.
Architecture
Internet → :8080 → Caddy (reverse proxy) → backend:8000 → FastAPI / Jinja2
- Caddy — TLS termination and reverse proxy to the backend
- Backend — FastAPI app rendering the home page and a
POST /api/contactendpoint - Static assets — served by FastAPI via a
StaticFilesmount at/static
Routes: / (home), /api/contact, /robots.txt, /sitemap.xml, /favicon.ico.
Everything else returns the styled 404 page.
Quick start (Docker Compose)
Prerequisites: Docker + Docker Compose v2.
cp .env.example .env # optional — defaults work out of the box
docker compose up -d --build
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/ # → 200
The site is at http://localhost:8080.
Production (automatic HTTPS)
Point the Caddyfile at your domain:
your-domain.com {
reverse_proxy backend:8000
}
Then launch with the production overlay (publishes 80/443, persists certs):
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
Set SITE_BASE_URL=https://your-domain.com in .env so sitemap.xml and
robots.txt emit the right URLs.
Configuration
All settings are environment variables (see .env.example); all are optional with sensible defaults.
| Variable | Purpose |
|---|---|
SITE_BASE_URL |
Public base URL used in sitemap.xml / robots.txt |
CONTACT_EMAIL / CONTACT_PHONE / CONTACT_LOCATION |
Contact details shown on the site |
AFTER_HOURS_PHONE |
Optional after-hours number |
NOTIFY_TO |
Where contact-form submissions are emailed |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM |
SMTP delivery for the contact form |
If SMTP isn't configured, contact submissions are logged to the container output instead of emailed — the form still returns success to the visitor.
Local development
cd backend
pip install -r requirements.txt
TEMPLATES_DIR=templates STATIC_DIR=static uvicorn main:app --reload --port 8000
Tests and lint:
pytest backend/tests
ruff check backend