diff --git a/astro.config.mjs b/astro.config.mjs index 6f257de7..ca2259dc 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -14,7 +14,7 @@ export default defineConfig({ "/components/html2rss-configs": "/creating-custom-feeds/", "/components": "/", "/web-application/how-to/deployment": "/web-application/deployment/", - "/web-application/how-to/automatic-updates": "/web-application/deployment/#auto-update-with-watchtower", + "/web-application/how-to/automatic-updates": "/web-application/deployment/#updating-the-stack", "/web-application/how-to/use-automatic-feed-generation": "/web-application/guides/use-the-feed-directory/", "/web-application/how-to/use-automatic-feed-generation/": diff --git a/src/components/docs/DockerComposeSnippet.astro b/src/components/docs/DockerComposeSnippet.astro index ee161f5c..8491801d 100644 --- a/src/components/docs/DockerComposeSnippet.astro +++ b/src/components/docs/DockerComposeSnippet.astro @@ -1,42 +1,85 @@ --- import { Code } from "@astrojs/starlight/components"; -import { botasaurusImage, caddyImage, watchtowerImage, webImage } from "../../data/docker"; +import { botasaurusImage, browserlessImage, caddyImage, webImage } from "../../data/docker"; interface Props { - variant: "minimal" | "productionCaddy" | "secure" | "watchtower" | "resourceGuardrails"; + variant: "minimal" | "productionCaddy" | "resourceGuardrails"; } const { variant } = Astro.props; const snippets: Record = { minimal: `services: - html2rss-web: - image: ${webImage} + caddy: + image: ${caddyImage} + restart: unless-stopped + depends_on: + - html2rss-web ports: - "127.0.0.1:4000:4000" + volumes: + - caddy_data:/data + - caddy_config:/config + command: + - sh + - -c + - | + caddy run --config - --adapter caddyfile < = { environment: RACK_ENV: production PORT: 4000 + AUTO_SOURCE_ENABLED: \${AUTO_SOURCE_ENABLED:-false} + HTML2RSS_ACCESS_TOKEN: \${HTML2RSS_ACCESS_TOKEN:-} HTML2RSS_SECRET_KEY: \${HTML2RSS_SECRET_KEY:?set HTML2RSS_SECRET_KEY} - HTML2RSS_ACCESS_TOKEN: \${HTML2RSS_ACCESS_TOKEN:?set HTML2RSS_ACCESS_TOKEN} - AUTO_SOURCE_ENABLED: "true" + HEALTH_CHECK_TOKEN: \${HEALTH_CHECK_TOKEN:?set HEALTH_CHECK_TOKEN} SENTRY_DSN: \${SENTRY_DSN:-} - BOTASAURUS_SCRAPER_URL: http://botasaurus:4010 - - botasaurus: - image: ${botasaurusImage} - restart: unless-stopped + SENTRY_ENABLE_LOGS: \${SENTRY_ENABLE_LOGS:-false} + HTML2RSS_TOTAL_TIMEOUT_SECONDS: 25 + RACK_TIMEOUT_SERVICE_TIMEOUT: 30 + BROWSERLESS_IO_WEBSOCKET_URL: ws://browserless:4002 + BROWSERLESS_IO_API_TOKEN: \${BROWSERLESS_IO_API_TOKEN:?set BROWSERLESS_IO_API_TOKEN} -volumes: - caddy_data:`, - secure: `services: - html2rss-web: - image: ${webImage} + browserless: + image: ${browserlessImage} restart: unless-stopped - env_file: - - path: .env - required: false + ports: + - "127.0.0.1:4002:4002" environment: - RACK_ENV: production - PORT: 4000 - HTML2RSS_SECRET_KEY: \${HTML2RSS_SECRET_KEY:?set HTML2RSS_SECRET_KEY} - HTML2RSS_ACCESS_TOKEN: \${HTML2RSS_ACCESS_TOKEN:?set HTML2RSS_ACCESS_TOKEN} - AUTO_SOURCE_ENABLED: "true" - SENTRY_DSN: \${SENTRY_DSN:-} - BOTASAURUS_SCRAPER_URL: http://botasaurus:4010 + PORT: 4002 + CONCURRENT: 10 + TOKEN: \${BROWSERLESS_IO_API_TOKEN:?set BROWSERLESS_IO_API_TOKEN} - botasaurus: - image: ${botasaurusImage} - restart: unless-stopped`, - watchtower: `services: - watchtower: - image: ${watchtowerImage} - restart: unless-stopped - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - # Optional for private registries only: - # - "\${HOME}/.docker/config.json:/config.json:ro" - command: --cleanup --interval 7200 html2rss-web botasaurus caddy`, +volumes: + caddy_data: + caddy_config:`, resourceGuardrails: `services: html2rss-web: image: ${webImage} diff --git a/src/content/docs/web-application/deployment.mdx b/src/content/docs/web-application/deployment.mdx index 0679926c..cd8a73c5 100644 --- a/src/content/docs/web-application/deployment.mdx +++ b/src/content/docs/web-application/deployment.mdx @@ -37,15 +37,9 @@ If you plan to enable automatic feed generation, also prepare: - a clear way to give users the same `HTML2RSS_ACCESS_TOKEN` your instance expects - optional advanced-rendering infrastructure only if harder sites later prove they need it -### Why a Reverse Proxy? +### Configure Caddy & SSL (Automatic HTTPS) -A reverse proxy terminates public HTTPS traffic and forwards requests to html2rss-web on your private Docker network. - -### Option A: Caddy (Automatic HTTPS) - -Caddy handles certificates and redirects. - - +The default `docker-compose.yml` includes Caddy out-of-the-box as the entry point. Caddy automatically terminates public HTTPS traffic, manages SSL/TLS certificates (via Let's Encrypt / ZeroSSL), redirects HTTP to HTTPS, and sanitizes headers to mitigate IP spoofing rate-limit bypasses. Create a `.env` file beside your compose file: @@ -56,16 +50,19 @@ Create a `.env` file beside your compose file: HTML2RSS_ACCESS_TOKEN= # Optional only if you want authenticated GET /api/v1/health # HEALTH_CHECK_TOKEN= + # Optional CDN/Load-balancer trusted proxies configuration + # CADDY_GLOBAL_OPTIONS="servers { trusted_proxies static 172.16.0.0/12 }" `} lang="dotenv" /> Before starting the stack: -- Set `CADDY_HOST` for your domain. +- Set `CADDY_HOST` for your domain. If unset or left as `localhost`, Caddy will run with a local self-signed certificate. - Generate `HTML2RSS_SECRET_KEY` with `openssl rand -hex 32`. - Set a strong `HTML2RSS_ACCESS_TOKEN`. This is the token users paste into the web UI. - Leave `HEALTH_CHECK_TOKEN` unset unless you intentionally use authenticated `GET /api/v1/health`. +- If you run behind a CDN (like Cloudflare), define `CADDY_GLOBAL_OPTIONS` in your `.env` with your trusted proxy configurations. - Leave `BUILD_TAG` and `GIT_SHA` unset unless you intentionally override image metadata in logs. - Adjust optional knobs such as `AUTO_SOURCE_ENABLED` and `SENTRY_DSN` as needed; refer to the [environment reference](/web-application/reference/env-variables/) for details. @@ -83,8 +80,6 @@ Harden the application before inviting other users: - Give trusted users the same current `HTML2RSS_ACCESS_TOKEN` through your normal operator channel - If you use authenticated `GET /api/v1/health`, set a strong `HEALTH_CHECK_TOKEN`; do not leave `CHANGE_ME_HEALTH_CHECK_TOKEN` anywhere in production - - Store these variables in a `.env` file and reference it with `env_file:` as demonstrated in the Caddy example. ## Operate & Monitor @@ -92,18 +87,20 @@ Store these variables in a `.env` file and reference it with `env_file:` as demo Keep the instance healthy once it is in production: - Review `docker compose logs` regularly for feed errors or certificate renewals -- Enable automatic image updates for the Docker tag you selected +- Keep your instance updated to pull the latest security fixes and features - Right-size CPU and memory to avoid starvation when parsing large feeds - Use `GET /api/v1/health/ready` for standard readiness checks - Add authenticated `GET /api/v1/health` only when your operator tooling needs it -### Auto-update with Watchtower +### Updating the Stack - +To update your containers to the latest version, run: -This Watchtower shape scopes updates to `html2rss-web`, `botasaurus`, and `caddy`; change the service names if your stack differs. +```sh +docker compose pull && docker compose up -d +``` -Check `docker compose logs watchtower` occasionally to confirm updates are applied. +We recommend running the image with the `:1` major-version tag (e.g., `html2rss/web:1`). This ensures the `pull` command retrieves new patch and minor releases under major version 1, delivering bug fixes and improvements without the risk of breaking changes. ### Resource Guardrails diff --git a/src/content/docs/web-application/getting-started.mdx b/src/content/docs/web-application/getting-started.mdx index b047870a..6c02a88a 100644 --- a/src/content/docs/web-application/getting-started.mdx +++ b/src/content/docs/web-application/getting-started.mdx @@ -15,7 +15,7 @@ Run `html2rss-web` locally with Docker, confirm one generated feed works, then m After this guide, you should have: -- `html2rss-web` running at `http://localhost:4000` +- `html2rss-web` running at `https://localhost:4000` - successful feed generated from your own page URL - a clear handoff from the demo stack to the real deployment setup - a generated feed URL from your own page URL @@ -59,7 +59,9 @@ This first-run stack keeps the path narrow: ## First Success Check -1. Open `http://localhost:4000` +1. Open `https://localhost:4000` + > [!NOTE] + > Because the local stack runs securely over HTTPS using Caddy's internal certificate authority, your browser will show a warning about an untrusted or self-signed certificate. You can safely bypass/accept the warning to access the interface. 2. Paste your own page URL into `Page URL` 3. Start with a listing, newsroom, changelog, releases, or updates page instead of a homepage 4. Enter `CHANGE_ME_ADMIN_TOKEN` when prompted diff --git a/src/content/docs/web-application/reference/versioning-and-releases.mdx b/src/content/docs/web-application/reference/versioning-and-releases.mdx index bf1dcbe4..40e7c25f 100644 --- a/src/content/docs/web-application/reference/versioning-and-releases.mdx +++ b/src/content/docs/web-application/reference/versioning-and-releases.mdx @@ -20,4 +20,4 @@ Use an exact version tag when you need fully pinned deploys. Use `latest` only w Release images include SBOM and provenance metadata. The image build also sets `BUILD_TAG` to the release version and `GIT_SHA` to the released commit. -If you use Docker Compose, [set up Watchtower](/web-application/deployment/#auto-update-with-watchtower) to pull updates for the tag you selected. +If you use Docker Compose, refer to [Updating the Stack](/web-application/deployment/#updating-the-stack) to pull updates for the tag you selected. diff --git a/src/data/docker.ts b/src/data/docker.ts index 014f2d69..fd72aa66 100644 --- a/src/data/docker.ts +++ b/src/data/docker.ts @@ -3,5 +3,4 @@ export const dockerHubUrl = `https://hub.docker.com/r/${dockerHubRepository}`; export const webImage = `${dockerHubRepository}:1`; export const browserlessImage = 'ghcr.io/browserless/chromium'; export const caddyImage = 'caddy:2-alpine'; -export const watchtowerImage = 'containrrr/watchtower'; export const botasaurusImage = 'html2rss/botasaurus-scrape-api:latest';