Files

131 lines
5.1 KiB
Markdown
Raw Permalink Normal View History

# Blog
2026-09-11 11:25:14 +02:00
Blog platform: a public website built with Nuxt, and a private PocketBase CMS where the
articles are written. Everything runs behind Caddy via Docker Compose.
```text
2026-09-11 11:25:14 +02:00
Browser → Caddy ─┬─ /_/*, /api/* → PocketBase (CMS)
└─ everything else → Nuxt (website), which also redirects /admin → /_/
```
2026-09-11 11:25:14 +02:00
There are no front-end accounts: only the superuser writes content. An article is a
title, a Markdown body, a cover image and a category.
## Development
No domain and no TLS needed — the services are published on localhost instead of going
through Caddy. Requires Docker.
**1. Create the environment file**
```bash
cp .env.example .env
```
2026-09-11 11:25:14 +02:00
**2. Set the admin credentials.** Replace `POCKETBASE_ADMIN_EMAIL` and
`POCKETBASE_ADMIN_PASSWORD` in `.env` with your own — this is the superuser account
PocketBase creates (or updates) on every start. The domain and URL variables can stay as
they are for local use.
```bash
2026-08-25 11:45:41 +02:00
chmod 600 .env
```
**3. Start the stack**
```bash
2026-09-11 11:25:14 +02:00
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build pocketbase frontend
```
The first build takes a few minutes. `docker-compose.dev.yml` publishes the ports on
`127.0.0.1` and leaves Caddy out; it must always be passed explicitly, so it can never be
picked up by accident in production.
2026-09-11 11:25:14 +02:00
**4. Open the admin UI** at http://localhost:3000/admin (redirects to PocketBase's dashboard
— this works in dev too, without Caddy, since the redirect is handled by the frontend itself)
and log in with the credentials from `.env`.
2026-09-11 11:25:14 +02:00
**5. Write something.** In the admin UI: create a **categories** record, then an
**articles** record (the `content` field is Markdown), then set `publishedAt` — the
website only shows articles whose `publishedAt` is set and not in the future.
**6. Open the website** at http://localhost:3000 — home, `/blog`, `/blog/<slug>` and
`/category/<slug>`.
Useful commands:
```bash
2026-09-11 11:25:14 +02:00
docker compose logs -f pocketbase # follow the CMS logs
docker compose -f docker-compose.yml -f docker-compose.dev.yml restart frontend
docker compose down # stop, keep the data
2026-09-11 11:25:14 +02:00
docker compose down -v # stop and WIPE the CMS database and media
```
2026-09-11 11:25:14 +02:00
To iterate on the frontend without rebuilding an image every time: `cd frontend && npm run
dev`. It defaults to `http://localhost:8090` for PocketBase, so it works against the
containerised CMS as is; override it with `NUXT_POCKETBASE_URL` if needed.
## Production
**1. Point the DNS at the server.** One `A` record (and `AAAA` if you have IPv6) on the
public IP of the machine:
| Record | Purpose |
|---|---|
2026-09-11 11:25:14 +02:00
| `example.com` | the website, and, at `/admin`, the PocketBase admin UI |
Wait for the record to resolve before starting the stack — Caddy requests the
certificate on the first boot and a failed challenge means a retry delay.
**2. Open the firewall** for ports `80` and `443` only. Port `80` is required: Caddy uses
2026-09-11 11:25:14 +02:00
it for the ACME challenge and to redirect to HTTPS. The admin UI shares port 443 with the
site (reachable by anyone who knows `/admin`, protected only by the superuser login, so
keep the password strong); PocketBase itself is never published directly, only Caddy's
proxy to it.
**3. Configure the environment.** Copy `.env.example` to `.env` on the server and set:
| Variable | Value |
|---|---|
| `PUBLIC_DOMAIN` | `example.com` |
| `ACME_EMAIL` | a mailbox you read — Let's Encrypt sends expiry warnings there |
| `PUBLIC_SITE_URL` | `https://example.com` |
2026-09-11 11:25:14 +02:00
| `PUBLIC_POCKETBASE_URL` | `https://example.com` — same origin, Caddy proxies `/_/` and `/api/` there |
| `POCKETBASE_URL` | leave it as `http://pocketbase:8090` — internal address, never public |
| `POCKETBASE_ADMIN_EMAIL` / `POCKETBASE_ADMIN_PASSWORD` | your real superuser credentials |
2026-09-11 11:25:14 +02:00
Keep `.env` out of version control; it is already ignored.
2026-09-11 11:25:14 +02:00
> Changing `POCKETBASE_ADMIN_PASSWORD` later and restarting rotates the superuser
> password immediately (the entrypoint upserts it on every boot) — a credential change,
> so treat it with the same care as any production credential rotation.
**4. Start everything**
```bash
docker compose up -d --build
```
2026-09-11 11:25:14 +02:00
This time Caddy is included: it serves both the website and, under `/admin`, the CMS
admin UI on `PUBLIC_DOMAIN`, obtains and renews the TLS certificate on its own, and adds
HSTS and the other security headers.
2026-09-11 11:25:14 +02:00
**5. Log into the admin UI** at `https://example.com/admin` with the credentials from
`.env`, then publish as in development.
2026-09-11 11:25:14 +02:00
**6. Back up what is not in git**: the `pocketbase-data` volume (database and uploaded
media together). Nothing else on the server holds state.
```bash
2026-09-11 11:25:14 +02:00
docker run --rm -v blog_pocketbase-data:/data -v "$PWD:/out" alpine tar czf /out/pocketbase-data.tar.gz -C /data .
```
**Updating a running site**: pull the new code, then `docker compose up -d --build`.
2026-09-11 11:25:14 +02:00
PocketBase applies new `pb_migrations/` files at startup; take a backup first.
Architecture, conventions and constraints are documented in [CLAUDE.md](CLAUDE.md).
## License
Proprietary — Copyright (c) 2026 Davide Grilli. All rights reserved. See [LICENSE](LICENSE).