Files
hantim-server/ARCHITECTURE.md
T
timothykim 5c38b0631a
Deploy nginx / deploy (push) Failing after 3s
Deploy timothykim.net / deploy (push) Successful in 3s
replace NPM with vanilla nginx + certbot
- 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
2026-03-16 00:55:13 -04:00

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.sh in this repo
  • This repo is cloned to /opt/hantim
  • The deploy user 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 shared Docker network
  • nginx proxies https://www.<domain> to the app's container by name
  • The shared network is created by the nginx compose file and referenced as external: true by 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>