Files
dev_blog/CONTAINER.md
T
2026-04-29 15:44:47 -05:00

93 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Container deployment
This stack runs the Astro blog behind a Caddy reverse proxy with the
[Coraza](https://coraza.io/) WAF (loaded with the OWASP Core Rule Set) and the
Google Cloud DNS plugin for ACME DNS-01 certificates.
## Layout
```
.
├── Containerfile # App image (node:24-trixie-slim → astro preview)
├── caddy/
│ ├── Containerfile # xcaddy build: Coraza + googleclouddns
│ ├── Caddyfile # Reverse proxy + WAF + TLS config
│ └── coraza.conf # Local Coraza overrides
├── compose.yaml # podman compose / docker compose entrypoint
└── quadlet/ # Systemd Quadlet units (production)
├── dev-blog.network
├── dev-blog-app.container
├── dev-blog-caddy.container
├── caddy-data.volume
└── caddy-config.volume
```
## Quick start (compose)
`compose.override.yaml` is auto-loaded by `podman compose`, so the default
invocation is **dev mode** (high ports, rootless-friendly, self-signed HTTPS):
```sh
podman compose up --build
# → http://localhost:8080
# → https://localhost:8443 (self-signed via Caddy `tls internal`)
```
The dev override bind-mounts `caddy/Caddyfile.dev` into the Caddy container
which uses `tls internal` instead of ACME, so HTTPS works locally without
needing a real domain or GCP credentials. Your browser will warn about the
self-signed cert; trust it for `localhost` if you want a clean page.
For **production** (privileged ports 80/443, ACME via Cloud DNS) skip the
override file with an explicit `-f`:
```sh
# Provide a Google Cloud service-account key with Cloud DNS admin on your zone:
mkdir -p secrets && cp /path/to/key.json secrets/gcp-dns.json
# Override the public hostname / project:
export SITE_ADDRESS=https://blog.example.com
export ACME_EMAIL=you@example.com
export GCP_PROJECT=my-gcp-project
podman compose -f compose.yaml up -d --build
```
Either mode also accepts ad-hoc port overrides via `HTTP_PORT`/`HTTPS_PORT`
environment variables.
## Production (Quadlet)
Copy the unit files into a Quadlet search path and reload systemd:
```sh
# rootful
sudo cp quadlet/* /etc/containers/systemd/
sudo systemctl daemon-reload
sudo systemctl start dev-blog-caddy.service # pulls in app + network
# rootless
mkdir -p ~/.config/containers/systemd
cp quadlet/* ~/.config/containers/systemd/
systemctl --user daemon-reload
systemctl --user start dev-blog-caddy.service
```
Build the images first so the Quadlet units can find them locally:
```sh
podman build -t localhost/dev-blog-app:latest .
podman build -t localhost/dev-blog-caddy:latest ./caddy
podman secret create gcp-dns-sa /path/to/key.json
```
## Notes
- The app container is *not* published to the host – Caddy reaches it on the
internal `dev-blog` network at `app:4321`.
- The OWASP CRS (v4.7.0 by default) is baked into the Caddy image; bump
`CRS_VERSION` in `caddy/Containerfile` to upgrade.
- Coraza's `load_owasp_crs` directive in the Caddyfile enables the CRS rules
that cover the OWASP Top 10 (injection, XSS, RCE, LFI/RFI, scanner detection,
protocol violations, session fixation, etc.).