Tracks deploying the website and docs static apps separately via Cloudflare Pages. Moved out of ui/apps/website/DEPLOY-PLAN.md, which was an internal planning note removed from the repo in #6471. Paths and the production branch have been corrected to the current monorepo layout (ui/..., master).
Context
The website (site + blog) and docs are static apps inside the ShellHub monorepo. Today everything runs via Docker Compose alongside the main app. The goal is to deploy the static site separately via Cloudflare Pages, independent of the product's release cycle so that publishing a blog post does not rebuild the app and shipping a new app version does not rebuild the site.
Architecture
shellhub/ (monorepo)
│
├── ui/apps/website → Cloudflare Pages (shellhub.io)
├── ui/apps/docs → Cloudflare Pages (docs.shellhub.io)
│
├── api/ ─┐
├── ssh/ │→ Docker Compose (app.shellhub.io)
├── gateway/ │ Separate deploy via release tags
├── ui/ ─┘
Cloudflare Pages configuration
Project 1: shellhub-website
| Field |
Value |
| Project name |
shellhub-website |
| Repository |
shellhub-io/shellhub |
| Production branch |
master |
| Root directory |
ui |
| Build command |
npm run build --workspace=apps/website |
| Output directory |
apps/website/dist |
| Custom domain |
shellhub.io |
| Node version (env var) |
NODE_VERSION=22 |
Project 2: shellhub-docs
| Field |
Value |
| Project name |
shellhub-docs |
| Repository |
shellhub-io/shellhub |
| Production branch |
master |
| Root directory |
ui |
| Build command |
npm run build --workspace=apps/docs |
| Output directory |
apps/docs/dist |
| Custom domain |
docs.shellhub.io |
| Node version (env var) |
NODE_VERSION=22 |
Deploy flow
Blog post / site content
A writer creates a post.mdx in the repo and opens a PR; Cloudflare generates an automatic preview deploy (a temporary URL like abc123.shellhub-website.pages.dev). After content review on the preview, the PR is merged to master; Cloudflare detects the push, builds apps/website, and deploys in ~30-60s, going live at shellhub.io/blog/<slug>.
Docs
Same flow. Changes under apps/docs/ trigger a rebuild of the shellhub-docs project.
Main app (API, SSH, Gateway, UI)
Independent flow — stays on Docker Compose / release tags as today. Cloudflare Pages is unaffected.
Blog — content structure
The blog uses MDX files in the repository:
ui/apps/website/
├── src/
│ ├── pages/
│ │ └── blog/
│ │ ├── index.astro # Post listing
│ │ └── [slug].astro # Individual post
│ ├── layouts/
│ │ └── BlogLayout.astro # Post layout
│ └── components/
│ └── blog/ # Blog components
├── content/
│ └── blog/ # MDX posts
└── astro.config.ts # Content collections config
Post frontmatter
---
title: "Post Title"
description: "Brief description for SEO and cards"
author: "username"
date: 2026-02-13
categories:
- product
tags:
- ssh
- security
image: "./cover.png"
draft: false
---
Astro content collections
Astro natively supports content collections with Zod schema validation:
// src/content.config.ts
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string(),
author: z.string(),
date: z.date(),
categories: z.array(z.string()).optional(),
tags: z.array(z.string()).optional(),
image: z.string().optional(),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
Considerations
- Monorepo build: Cloudflare Pages runs
npm install from the configured root directory (ui). Since we use npm workspaces, the install resolves dependencies for all apps; the build command with --workspace=apps/website builds only the desired app.
- Environment variables: configure
NODE_VERSION=22 and any required VITE_SHELLHUB_* (e.g. VITE_SHELLHUB_CLOUD=true) in the Cloudflare Pages dashboard.
- Preview deploys: each PR automatically generates a preview deploy with a unique URL — useful for reviewing blog posts and site changes before merging.
- Rollback: Cloudflare keeps deploy history; instant rollback to any previous version via the dashboard.
- Free tier covers this: unlimited builds and bandwidth, 500 deploys/month, automatic per-PR preview deploys, global CDN with edge caching, custom domains with automatic SSL, and monorepo support.
TODO
Tracks deploying the
websiteanddocsstatic apps separately via Cloudflare Pages. Moved out ofui/apps/website/DEPLOY-PLAN.md, which was an internal planning note removed from the repo in #6471. Paths and the production branch have been corrected to the current monorepo layout (ui/...,master).Context
The website (site + blog) and docs are static apps inside the ShellHub monorepo. Today everything runs via Docker Compose alongside the main app. The goal is to deploy the static site separately via Cloudflare Pages, independent of the product's release cycle so that publishing a blog post does not rebuild the app and shipping a new app version does not rebuild the site.
Architecture
Cloudflare Pages configuration
Project 1:
shellhub-websiteshellhub-websiteshellhub-io/shellhubmasteruinpm run build --workspace=apps/websiteapps/website/distshellhub.ioNODE_VERSION=22Project 2:
shellhub-docsshellhub-docsshellhub-io/shellhubmasteruinpm run build --workspace=apps/docsapps/docs/distdocs.shellhub.ioNODE_VERSION=22Deploy flow
Blog post / site content
A writer creates a
post.mdxin the repo and opens a PR; Cloudflare generates an automatic preview deploy (a temporary URL likeabc123.shellhub-website.pages.dev). After content review on the preview, the PR is merged tomaster; Cloudflare detects the push, buildsapps/website, and deploys in ~30-60s, going live atshellhub.io/blog/<slug>.Docs
Same flow. Changes under
apps/docs/trigger a rebuild of theshellhub-docsproject.Main app (API, SSH, Gateway, UI)
Independent flow — stays on Docker Compose / release tags as today. Cloudflare Pages is unaffected.
Blog — content structure
The blog uses MDX files in the repository:
Post frontmatter
Astro content collections
Astro natively supports content collections with Zod schema validation:
Considerations
npm installfrom the configured root directory (ui). Since we use npm workspaces, the install resolves dependencies for all apps; the build command with--workspace=apps/websitebuilds only the desired app.NODE_VERSION=22and any requiredVITE_SHELLHUB_*(e.g.VITE_SHELLHUB_CLOUD=true) in the Cloudflare Pages dashboard.TODO
shellhub-websiteproject on Cloudflare Pagesshellhub-docsproject on Cloudflare Pages_redirectsfile)