- nginx:alpine replaces nginx-proxy-manager - certbot on host with standalone issuance and auto-renewal - per-site nginx configs in conf.d/ (HTTP->HTTPS, bare->www) - container names use underscores (timothykim_net) - setup.sh: automated cert issuance, dynamic app startup - deploy.sh: nginx -t guard, graceful image-not-found - new-app.sh: Gitea API repo creation, nginx conf generation - add ARCHITECTURE.md, USECASES.md, CLAUDE.md - remove old NPM data/certs from tracking
4.2 KiB
Architecture
Servers
Hantim server (hantim)
The production application server. Runs Rocky Linux 9 on Vultr.
- Hosts all app containers and the nginx reverse proxy via Docker Compose
- Provisioned by
setup.shin this repo - This repo is cloned to
/opt/hantim - The
deployuser receives SSH commands from CI to trigger deploys - Certbot runs on the host (not in Docker) and manages Let's Encrypt certs
Gitea server (git.timothykim.net)
A separate server running the Gitea instance. Provides:
- Git hosting for all repos
- Docker container registry (
git.timothykim.net/hantim/<app>) - Gitea Actions CI/CD (runner registered at site level)
- Organization-level secrets and variables shared across repos
Bitwarden (cloud)
Bitwarden free tier is used as the secret store. Stores:
hantim-git-crypt-key-- git-crypt symmetric key (Secure Note, base64)hantim-ci-registry-push-- Gitea API token for registry access (Secure Note)hantim-server-deploy-- deploy user SSH key pair (SSH Key)hantim-new-app-script-- Gitea API token for creating repos (Secure Note)
The Bitwarden CLI (bw) is used in setup.sh (on the server) and
new-app.sh (on dev machines) to fetch secrets at runtime.
Repos
All repos live under the hantim organization on Gitea.
hantim-server
This repo. Contains server provisioning, Docker Compose configs, nginx configs, deploy scripts, and Gitea Actions workflows.
setup.sh # Server provisioning (idempotent)
scripts/deploy.sh # Receives deploy commands via SSH
scripts/new-app.sh # Scaffolds a new app (Gitea repo + local files)
docker/nginx/ # nginx reverse proxy
compose.yml # nginx:alpine container
nginx.conf # Main nginx config
conf.d/<domain>.conf # Per-app server blocks
docker/<domain>/compose.yml # Per-app compose files
.gitea/workflows/deploy-<app>.yml # Per-app deploy workflows
static-site-template
A Gitea template repo used by new-app.sh to scaffold new static sites.
Contains a Dockerfile, nginx config, placeholder HTML, and a build.yml
workflow that builds/pushes a Docker image and triggers a deploy.
Uses Gitea template variables (${REPO_NAME}) so the image tag and deploy
command are automatically set to the repo name.
App repos (e.g., timothykim.net)
Each app is its own repo created from static-site-template. Contains the
app source code and a build.yml workflow.
Network topology
Internet
|
[Hantim Server]
|
nginx (port 80/443)
| |
timothykim_net hcsuzuki_net ...
(shared Docker network)
- nginx is the only container with host port bindings (80, 443)
- All app containers are on the
sharedDocker network - nginx proxies
https://www.<domain>to the app's container by name - The
sharednetwork is created by the nginx compose file and referenced asexternal: trueby app compose files
Deploy flow
App code change (push to app repo)
Developer pushes to app repo (e.g., timothykim.net)
-> build.yml workflow runs on Gitea runner
-> docker build + push to git.timothykim.net registry
-> SSH to deploy@hantim as deploy-<domain>
-> deploy.sh: git pull, docker compose pull, docker compose up -d
-> (if image not yet in registry, skips gracefully)
Infrastructure change (push to hantim-server)
Developer pushes to hantim-server
-> deploy-<app>.yml triggers (based on changed paths under docker/<app>/)
-> SSH to deploy@hantim as deploy-<app>
-> deploy.sh: git pull, docker compose pull, docker compose up -d
-> (nginx only): test config with nginx -t before applying
Conventions
- App name = domain (e.g.,
hcsuzuki.net) - Container name = domain with dots replaced by underscores (e.g.,
hcsuzuki_net) - Cert name = domain (stored at
/etc/letsencrypt/live/<domain>/) - Image name =
git.timothykim.net/hantim/<domain>:latest - All HTTP traffic redirects to
https://www.<domain> - Bare domain HTTPS redirects to
https://www.<domain>