144 lines
6.1 KiB
Markdown
144 lines
6.1 KiB
Markdown
# mifi.holdings — Landing Page
|
||
|
||
Static landing site for **mifi.holdings** (and www). Plain HTML/CSS, no framework; served by **nginx** in Docker behind **Traefik**, with CI/CD via **Woodpecker** and deployment via **Portainer**.
|
||
|
||
---
|
||
|
||
## Quick reference
|
||
|
||
| What | Where |
|
||
| ----------------------- | -------------------------------------------- |
|
||
| **Site** | `mifi.holdings`, `www.mifi.holdings` (HTTPS) |
|
||
| **Runtime** | nginx (Alpine) in Docker |
|
||
| **Reverse proxy / TLS** | Traefik (Let's Encrypt) |
|
||
| **CI/CD** | Woodpecker (ci → build → deploy) |
|
||
| **Registry** | `git.mifi.dev/mifi-holdings/landing` |
|
||
| **Package manager** | pnpm |
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌─────────────────┐
|
||
│ Traefik │ (routing, TLS, websecure)
|
||
└────────┬────────┘
|
||
│
|
||
┌────────▼────────┐
|
||
│ Docker container │ mifi-holdings-landing
|
||
│ nginx:alpine │ port 80
|
||
│ /usr/share/ │
|
||
│ nginx/html ← │ static files from image
|
||
└─────────────────┘
|
||
```
|
||
|
||
- **Build**: Docker image = `nginx:alpine` + project `nginx/conf.d/` + `src/` copied into `/usr/share/nginx/html`.
|
||
- **Run**: Single service on external network `marina-net`; Traefik routes `mifi.holdings` and `www.mifi.holdings` to this container (HTTPS, `security-prison@file` middleware).
|
||
|
||
---
|
||
|
||
## Repo structure
|
||
|
||
```
|
||
.
|
||
├── src/ # Static site (served as-is)
|
||
│ ├── index.html
|
||
│ └── css/
|
||
│ └── style.css
|
||
├── nginx/
|
||
│ └── conf.d/
|
||
│ └── default.conf # nginx server config (cache rules, SPA fallback)
|
||
├── .woodpecker/
|
||
│ ├── ci.yml # Lint + format check (PR + push to main)
|
||
│ ├── build.yml # Build image, push to registry (main)
|
||
│ └── deploy.yml # Trigger Portainer redeploy (main)
|
||
├── docker-compose.yml # Service + Traefik labels for production
|
||
├── Dockerfile # nginx + config + src
|
||
├── package.json # pnpm scripts: format, lint, docker build/push
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
## Tech stack
|
||
|
||
- **Frontend**: Static HTML + CSS only (no JS build step).
|
||
- **Server**: nginx (Alpine) in Docker.
|
||
- **Tooling**: **pnpm** (format with Prettier, lint with yamllint for `.woodpecker/*.yml` and `docker-compose.yml`).
|
||
- **Deployment**: Docker image → Gitea registry → Portainer stack redeploy.
|
||
|
||
---
|
||
|
||
## Local development
|
||
|
||
- **Dependencies**: `pnpm install` (only devDependencies: Prettier, yaml-lint).
|
||
- **Format**: `pnpm format` / `pnpm format:check`.
|
||
- **Lint**: `pnpm lint` (yamllint on Woodpecker and docker-compose YAML).
|
||
- **Run locally (Docker)**:
|
||
- Build: `pnpm docker:build` (or `docker build --platform linux/amd64 -t git.mifi.dev/mifi-holdings/landing:latest .`).
|
||
- Run: use `docker-compose up` **only** on a host that has `marina-net` and Traefik; otherwise run the image with a port map and open `http://localhost:<port>`.
|
||
|
||
There is no dev server in this repo; edit `src/` and refresh the browser or rebuild the image to test.
|
||
|
||
---
|
||
|
||
## Docker
|
||
|
||
- **Dockerfile**: Copies `nginx/conf.d/` and `src/` into an `nginx:alpine` image. No multi-stage build; the image is the final runtime.
|
||
- **Image**: Tagged as `git.mifi.dev/mifi-holdings/landing:latest` (and `:<commit-sha>` in CI).
|
||
- **Local build/push**: `pnpm docker:build`, `pnpm docker:push` (requires login to `git.mifi.dev`).
|
||
|
||
---
|
||
|
||
## CI/CD (Woodpecker)
|
||
|
||
Three pipelines:
|
||
|
||
1. **ci** (`.woodpecker/ci.yml`)
|
||
- **When**: Every PR and every push to `main`.
|
||
- **Steps**: Install deps → Prettier format check → yamllint (Woodpecker + docker-compose). Mattermost notifications on success/failure.
|
||
|
||
2. **build** (`.woodpecker/build.yml`)
|
||
- **When**: Push/tag/manual on `main` (and deployment to production); **depends_on: ci**.
|
||
- **Steps**: Build Docker image (linux/amd64, up to 3 retries) → push `:latest` and `:<CI_COMMIT_SHA>` to `git.mifi.dev/mifi-holdings/landing`. Mattermost notifications.
|
||
|
||
3. **deploy** (`.woodpecker/deploy.yml`)
|
||
- **When**: Same as build (main / production deploy); **depends_on: ci**.
|
||
- **Steps**: Call Portainer webhook to redeploy the stack. Mattermost notifications.
|
||
|
||
Secrets used: `gitea_registry_username`, `gitea_package_token`, `portainer_webhook_url`, `mattermost_*` (bot token, channel IDs, API URL).
|
||
|
||
---
|
||
|
||
## Deployment (production)
|
||
|
||
- **Orchestration**: Stack defined in `docker-compose.yml`, deployed via **Portainer** (webhook triggered by Woodpecker deploy pipeline).
|
||
- **Network**: Container joins external network `marina-net` (shared with Traefik).
|
||
- **Traefik**:
|
||
- Hosts: `mifi.holdings`, `www.mifi.holdings`.
|
||
- Entrypoint: `websecure` (HTTPS).
|
||
- TLS: Let's Encrypt (`tls.certresolver=letsencrypt`).
|
||
- Middleware: `security-prison@file`.
|
||
- Backend: this service, port 80.
|
||
- **Healthcheck**: `wget --spider -q http://localhost/` every 20s (timeout 3s, 3 retries).
|
||
|
||
To deploy manually: pull the latest image and redeploy the stack in Portainer, or trigger the Portainer webhook (e.g. same URL as in `portainer_webhook_url`).
|
||
|
||
---
|
||
|
||
## nginx behavior
|
||
|
||
- **Root**: `/usr/share/nginx/html` (contents of `src/`).
|
||
- **HTML**: `Cache-Control: public, no-cache` so updates are visible quickly.
|
||
- **JS/CSS**: Long cache, `immutable` for hashed/versioned assets.
|
||
- **Images/fonts**: Cached (30d / 1y).
|
||
- **SPA-style fallback**: `/` tries `$uri`, `$uri/`, then `index.html`, then 404.
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
- **What it is**: Static landing for mifi.holdings.
|
||
- **How it runs**: nginx in Docker, fronted by Traefik on `marina-net`.
|
||
- **How it’s updated**: Push to `main` → Woodpecker runs ci, build (image + push), deploy (Portainer webhook); Mattermost reports status.
|