From 9de4f66466da998971484ecc858e1819cd0b9257 Mon Sep 17 00:00:00 2001 From: Guanzhou Song Date: Thu, 19 Mar 2026 11:06:01 -0400 Subject: [PATCH 1/4] refacotr blog section to use Jeklly --- .github/workflows/continuous-deployment.yml | 6 + .gitignore | 8 +- Gemfile | 5 + Gemfile.lock | 76 +++++ app/blogs/page.tsx | 74 ---- app/components/Card.tsx | 154 --------- app/components/Navbar.tsx | 6 +- app/services/blogService.ts | 11 - app/types/Post.ts | 8 - blogs/_config.yml | 14 + blogs/_data/categories.yml | 12 + blogs/{content.yml => _data/posts.yml} | 0 blogs/_includes/post-card.html | 38 +++ blogs/_layouts/default.html | 48 +++ blogs/assets/blog.css | 352 ++++++++++++++++++++ blogs/index.html | 25 ++ package.json | 4 +- readme.md | 31 +- 18 files changed, 612 insertions(+), 260 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock delete mode 100644 app/blogs/page.tsx delete mode 100644 app/components/Card.tsx delete mode 100644 app/services/blogService.ts delete mode 100644 app/types/Post.ts create mode 100644 blogs/_config.yml create mode 100644 blogs/_data/categories.yml rename blogs/{content.yml => _data/posts.yml} (100%) create mode 100644 blogs/_includes/post-card.html create mode 100644 blogs/_layouts/default.html create mode 100644 blogs/assets/blog.css create mode 100644 blogs/index.html diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index 6614670..18a80a3 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -55,6 +55,11 @@ jobs: with: node-version: 24 cache: npm + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3 + bundler-cache: true - name: Restore cache uses: actions/cache@v4 with: @@ -70,6 +75,7 @@ jobs: - name: Build with Next.js env: NEXT_BASE_PATH: ${{ github.event.repository.name }} + JEKYLL_BASE_PATH: /${{ github.event.repository.name }}/blogs run: npm run build - name: Download DocumentDB packages from latest release run: .github/scripts/download_packages.sh diff --git a/.gitignore b/.gitignore index ab74a61..d62659b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,12 @@ articles/**/*.yml /.next/ /out/ +# jekyll +/.bundle/ +/.jekyll-cache/ +/.sass-cache/ +/vendor/bundle/ + # production /build @@ -48,4 +54,4 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..3009d5d --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +source "https://rubygems.org" + +gem "ffi", "~> 1.15.5" +gem "jekyll", "~> 4.3.4" +gem "webrick", "~> 1.8" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..91f2cd8 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,76 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.9) + public_suffix (>= 2.0.2, < 8.0) + colorator (1.1.0) + concurrent-ruby (1.3.6) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.15.5) + forwardable-extended (2.6.0) + google-protobuf (3.23.4) + http_parser.rb (0.8.1) + i18n (1.14.8) + concurrent-ruby (~> 1.0) + jekyll (4.3.4) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-watch (2.2.1) + listen (~> 3.0) + kramdown (2.5.2) + rexml (>= 3.4.4) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.10.0) + logger + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + mercenary (0.4.0) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (5.1.1) + rake (13.3.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.4.4) + rouge (3.30.0) + safe_yaml (1.0.5) + sass-embedded (1.58.3) + google-protobuf (~> 3.21) + rake (>= 10.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.6.0) + webrick (1.9.2) + +PLATFORMS + ruby + +DEPENDENCIES + ffi (~> 1.15.5) + jekyll (~> 4.3.4) + webrick (~> 1.8) + +BUNDLED WITH + 1.17.2 diff --git a/app/blogs/page.tsx b/app/blogs/page.tsx deleted file mode 100644 index a42308b..0000000 --- a/app/blogs/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { getAllPosts } from '../services/blogService'; -import { Card } from '../components/Card'; -import { Post } from '../types/Post'; - -export default function Blogs() { - const posts: Post[] = getAllPosts(); - const featuredPosts = posts.filter(post => post.featured); - const regularPosts = posts.filter(post => !post.featured); - - return ( -
- {/* Artistic background elements */} -
-
-
-
-
-
-
- - {/* Floating particles */} -
-
-
-
-
- -
- {/* Header */} -
-

- Latest from our Blog -

-

- Insights, updates, and deep dives into the world of document databases and open-source innovation -

-
-
- - - {/* Blog Grid */} -
- {/* Featured Posts */} - {featuredPosts.map((post, index) => ( - - ))} - - {/* Regular Posts */} - {regularPosts.map((post, index) => ( - - ))} -
-
-
- ); -} \ No newline at end of file diff --git a/app/components/Card.tsx b/app/components/Card.tsx deleted file mode 100644 index 2fe678d..0000000 --- a/app/components/Card.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { JSX } from 'react'; -import { Post } from '../types/Post'; - -export function Card({ post, featured = false }: { post: Post; featured?: boolean }) { - const icons: Record = { - 'microsoft-open-source-blog': ( - - ), - 'aws-blog': ( - - ), - 'azure-cosmos-db-blog': ( - - ), - 'yugabytedb-blog': ( - - ), - }; - - const icon = icons[post.category as keyof typeof icons]; - - const styles = { - 'microsoft-open-source-blog': { - label: 'Microsoft Open Source Blog', - timestamp: 'August 25, 2025', - gradientFrom: 'from-blue-500', - gradientTo: 'to-blue-600', - textColor: 'blue-400', - hoverColor: 'blue-300', - bgGradient: 'from-blue-500/10 to-purple-500/10', - borderHover: 'border-blue-500/50', - tagColors: [ - 'bg-blue-500/20 text-blue-400', - 'bg-purple-500/20 text-purple-400', - 'bg-green-500/20 text-green-400', - 'bg-orange-500/20 text-orange-400', - ], - }, - 'aws-blog': { - label: 'AWS Blogs', - timestamp: 'Recent', - gradientFrom: 'from-orange-500', - gradientTo: 'to-orange-600', - textColor: 'orange-400', - hoverColor: 'orange-300', - bgGradient: 'from-orange-500/10 to-amber-500/10', - borderHover: 'border-orange-500/50', - tagColors: [ - 'bg-orange-500/20 text-orange-400', - 'bg-amber-500/20 text-amber-400', - 'bg-yellow-500/20 text-yellow-400', - 'bg-blue-500/20 text-blue-400', - ], - }, - 'azure-cosmos-db-blog': { - label: 'Azure Cosmos DB Blog', - timestamp: 'Recent', - gradientFrom: 'from-purple-500', - gradientTo: 'to-purple-600', - textColor: 'purple-400', - hoverColor: 'purple-300', - bgGradient: 'from-purple-500/10 to-pink-500/10', - borderHover: 'border-purple-500/50', - tagColors: [ - 'bg-purple-500/20 text-purple-400', - 'bg-blue-500/20 text-blue-400', - 'bg-green-500/20 text-green-400', - 'bg-orange-500/20 text-orange-400', - ], - }, - 'yugabytedb-blog': { - label: 'YugabyteDB Blog', - timestamp: 'Partner Content', - gradientFrom: 'from-green-500', - gradientTo: 'to-green-600', - textColor: 'green-400', - hoverColor: 'green-300', - bgGradient: 'from-green-500/10 to-emerald-500/10', - borderHover: 'border-green-500/50', - tagColors: [ - 'bg-green-500/20 text-green-400', - 'bg-blue-500/20 text-blue-400', - 'bg-orange-500/20 text-orange-400', - 'bg-yellow-500/20 text-yellow-400', - ], - }, - }; - - const style = styles[post.category as keyof typeof styles]; - - return ( - - ); -} diff --git a/app/components/Navbar.tsx b/app/components/Navbar.tsx index 048c031..3441467 100644 --- a/app/components/Navbar.tsx +++ b/app/components/Navbar.tsx @@ -75,12 +75,12 @@ export default function Navbar() { > Download - Blogs - + diff --git a/app/services/blogService.ts b/app/services/blogService.ts deleted file mode 100644 index 82c58a8..0000000 --- a/app/services/blogService.ts +++ /dev/null @@ -1,11 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import yaml from 'js-yaml'; -import { Post } from '../types/Post'; - -export function getAllPosts(): Post[] { - const filePath = path.join(process.cwd(), 'blogs', 'content.yml'); - const fileContents = fs.readFileSync(filePath, 'utf8'); - const posts = yaml.load(fileContents) as Post[]; - return posts; -} \ No newline at end of file diff --git a/app/types/Post.ts b/app/types/Post.ts deleted file mode 100644 index 23419f0..0000000 --- a/app/types/Post.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface Post { - title: string; - category: string; - description: string; - tags: string[]; - featured?: boolean; - uri: string; -} \ No newline at end of file diff --git a/blogs/_config.yml b/blogs/_config.yml new file mode 100644 index 0000000..133e681 --- /dev/null +++ b/blogs/_config.yml @@ -0,0 +1,14 @@ +title: DocumentDB Blog +description: Insights, updates, and deep dives into the world of document databases and open-source innovation. +baseurl: /blogs +url: "" +permalink: pretty +markdown: kramdown +kramdown: + input: GFM +defaults: + - + scope: + path: "" + values: + layout: default diff --git a/blogs/_data/categories.yml b/blogs/_data/categories.yml new file mode 100644 index 0000000..682eb6f --- /dev/null +++ b/blogs/_data/categories.yml @@ -0,0 +1,12 @@ +microsoft-open-source-blog: + label: Microsoft Open Source Blog + meta: August 25, 2025 +aws-blog: + label: AWS Blogs + meta: Recent +azure-cosmos-db-blog: + label: Azure Cosmos DB Blog + meta: Recent +yugabytedb-blog: + label: YugabyteDB Blog + meta: Partner Content diff --git a/blogs/content.yml b/blogs/_data/posts.yml similarity index 100% rename from blogs/content.yml rename to blogs/_data/posts.yml diff --git a/blogs/_includes/post-card.html b/blogs/_includes/post-card.html new file mode 100644 index 0000000..2db22c5 --- /dev/null +++ b/blogs/_includes/post-card.html @@ -0,0 +1,38 @@ +{% assign post = include.post %} +{% assign category = site.data.categories[post.category] %} + + diff --git a/blogs/_layouts/default.html b/blogs/_layouts/default.html new file mode 100644 index 0000000..91df859 --- /dev/null +++ b/blogs/_layouts/default.html @@ -0,0 +1,48 @@ + + + + + + {% if page.title %}{{ page.title }} · {% endif %}DocumentDB + + + + + + {% assign site_root = site.baseurl | replace: '/blogs', '' %} + {% if site_root == '' %} + {% assign site_root = '/' %} + {% endif %} + + {% assign home_href = site_root | append: '/' | replace: '//', '/' %} + {% assign docs_href = site_root | append: '/docs/' | replace: '//', '/' %} + {% assign packages_href = site_root | append: '/packages/' | replace: '//', '/' %} + {% assign blogs_href = site.baseurl | append: '/' | replace: '//', '/' %} + +
+ + +
+ {{ content }} +
+ +
+ +
+
+ + diff --git a/blogs/assets/blog.css b/blogs/assets/blog.css new file mode 100644 index 0000000..0827f49 --- /dev/null +++ b/blogs/assets/blog.css @@ -0,0 +1,352 @@ +:root { + --page-bg: #09090b; + --surface: rgba(23, 23, 27, 0.84); + --surface-strong: #111827; + --surface-soft: rgba(38, 38, 45, 0.82); + --text: #f5f7fb; + --muted: #a7afbe; + --subtle: #7c8699; + --border: rgba(148, 163, 184, 0.16); + --shadow: rgba(2, 6, 23, 0.5); +} + +* { + box-sizing: border-box; +} + +html { + min-height: 100%; +} + +body { + margin: 0; + min-height: 100%; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color: var(--text); + background: + radial-gradient(circle at top left, rgba(59, 130, 246, 0.18), transparent 30%), + radial-gradient(circle at top right, rgba(168, 85, 247, 0.14), transparent 28%), + radial-gradient(circle at bottom center, rgba(34, 197, 94, 0.1), transparent 26%), + var(--page-bg); +} + +a { + color: inherit; + text-decoration: none; +} + +.site-shell { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.site-header { + position: sticky; + top: 0; + z-index: 40; + border-bottom: 1px solid var(--border); + background: rgba(9, 9, 11, 0.88); + backdrop-filter: blur(14px); +} + +.site-header__inner, +.site-footer__inner { + width: min(1100px, calc(100% - 2rem)); + margin: 0 auto; +} + +.site-header__inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; + padding: 1rem 0; +} + +.site-brand { + font-size: 1.35rem; + font-weight: 700; + letter-spacing: -0.02em; +} + +.site-nav { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + gap: 1rem; + color: var(--muted); +} + +.site-nav a { + transition: color 160ms ease; +} + +.site-nav a:hover, +.site-nav a[aria-current="page"] { + color: var(--text); +} + +.blog-main { + flex: 1; + padding: 4.5rem 1rem 5rem; +} + +.blog-hero, +.posts-list { + width: min(1100px, 100%); + margin: 0 auto; +} + +.blog-hero { + text-align: center; + margin-bottom: 2.75rem; +} + +.blog-hero__kicker { + margin: 0 0 0.85rem; + font-size: 0.95rem; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + color: #93c5fd; +} + +.blog-hero__title { + margin: 0; + font-size: clamp(2.75rem, 6vw, 4.5rem); + line-height: 1.05; + letter-spacing: -0.05em; +} + +.blog-hero__description { + max-width: 760px; + margin: 1.3rem auto 0; + font-size: clamp(1.05rem, 2vw, 1.25rem); + line-height: 1.7; + color: var(--muted); +} + +.blog-hero__note { + display: inline-flex; + margin-top: 1.6rem; + padding: 0.6rem 1rem; + border-radius: 999px; + border: 1px solid rgba(96, 165, 250, 0.25); + background: rgba(59, 130, 246, 0.08); + color: #cbd5e1; + font-size: 0.95rem; +} + +.posts-list { + display: grid; + gap: 1.35rem; +} + +.post-card { + --accent-rgb: 96, 165, 250; + --accent-alt-rgb: 37, 99, 235; + display: block; + position: relative; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 24px; + background: linear-gradient(180deg, rgba(17, 24, 39, 0.88), rgba(10, 10, 11, 0.94)); + box-shadow: 0 24px 60px -28px var(--shadow); + cursor: pointer; + transition: + transform 180ms ease, + border-color 180ms ease, + box-shadow 180ms ease, + color 180ms ease; +} + +.post-card::before { + content: ""; + position: absolute; + inset: -30%; + background: + radial-gradient(circle at top right, rgba(var(--accent-rgb), 0.22), transparent 24%), + radial-gradient(circle at bottom left, rgba(var(--accent-alt-rgb), 0.18), transparent 28%); + pointer-events: none; +} + +.post-card:hover, +.post-card:focus-visible { + transform: translateY(-3px); + border-color: rgba(var(--accent-rgb), 0.45); + box-shadow: 0 30px 65px -28px rgba(var(--accent-rgb), 0.22); +} + +.post-card:focus-visible { + outline: 3px solid rgba(var(--accent-rgb), 0.35); + outline-offset: 3px; +} + +.post-card__body { + position: relative; + padding: 1.75rem; +} + +.post-card--featured .post-card__body { + padding: 2.3rem; +} + +.post-card__header { + display: flex; + align-items: center; + gap: 0.95rem; + margin-bottom: 1.25rem; +} + +.post-card__icon { + flex: 0 0 auto; + width: 2.75rem; + height: 2.75rem; + border-radius: 0.95rem; + background: linear-gradient(135deg, rgba(var(--accent-rgb), 1), rgba(var(--accent-alt-rgb), 1)); + box-shadow: 0 18px 32px -20px rgba(var(--accent-rgb), 0.85); +} + +.post-card__source, +.post-card__meta { + margin: 0; +} + +.post-card__source { + font-size: 0.95rem; + font-weight: 600; + color: rgb(var(--accent-rgb)); +} + +.post-card__meta { + margin-top: 0.15rem; + color: var(--subtle); + font-size: 0.8rem; +} + +.post-card__title { + margin: 0; + font-size: 1.45rem; + line-height: 1.18; + letter-spacing: -0.03em; + transition: color 180ms ease; +} + +.post-card--featured .post-card__title { + font-size: clamp(1.9rem, 3vw, 2.5rem); +} + +.post-card__description { + margin: 1rem 0 0; + color: var(--muted); + line-height: 1.75; +} + +.post-card__tags { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + margin-top: 1.4rem; +} + +.post-card__tag { + display: inline-flex; + align-items: center; + padding: 0.45rem 0.8rem; + border-radius: 999px; + background: rgba(var(--accent-rgb), 0.14); + color: rgb(var(--accent-rgb)); + font-size: 0.8rem; + font-weight: 600; +} + +.post-card__link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin-top: 1.45rem; + font-weight: 600; + color: rgb(var(--accent-rgb)); +} + +.post-card:hover .post-card__title, +.post-card:focus-visible .post-card__title, +.post-card:hover .post-card__link, +.post-card:focus-visible .post-card__link { + color: rgb(var(--accent-alt-rgb)); +} + +.post-card__link span[aria-hidden="true"] { + transition: transform 180ms ease; +} + +.post-card:hover .post-card__link span[aria-hidden="true"], +.post-card:focus-visible .post-card__link span[aria-hidden="true"] { + transform: translateX(3px); +} + +.post-card--microsoft-open-source-blog { + --accent-rgb: 96, 165, 250; + --accent-alt-rgb: 147, 51, 234; +} + +.post-card--aws-blog { + --accent-rgb: 249, 115, 22; + --accent-alt-rgb: 245, 158, 11; +} + +.post-card--azure-cosmos-db-blog { + --accent-rgb: 168, 85, 247; + --accent-alt-rgb: 59, 130, 246; +} + +.post-card--yugabytedb-blog { + --accent-rgb: 34, 197, 94; + --accent-alt-rgb: 16, 185, 129; +} + +.site-footer { + border-top: 1px solid var(--border); + background: rgba(9, 9, 11, 0.9); +} + +.site-footer__inner { + padding: 1.2rem 0 1.4rem; + color: var(--subtle); + font-size: 0.85rem; + line-height: 1.6; + text-align: center; +} + +@media (max-width: 820px) { + .site-header__inner { + flex-direction: column; + align-items: flex-start; + } + + .site-nav { + justify-content: flex-start; + } + + .blog-main { + padding-top: 3rem; + } +} + +@media (max-width: 640px) { + .site-header__inner, + .site-footer__inner { + width: min(1100px, calc(100% - 1.5rem)); + } + + .post-card__body, + .post-card--featured .post-card__body { + padding: 1.35rem; + } + + .post-card__header { + align-items: flex-start; + } +} diff --git a/blogs/index.html b/blogs/index.html new file mode 100644 index 0000000..34c3e8c --- /dev/null +++ b/blogs/index.html @@ -0,0 +1,25 @@ +--- +title: Latest from our Blog +description: Insights, updates, and deep dives into the world of document databases and open-source innovation. +--- +{% assign featured_posts = site.data.posts | where: "featured", true %} +{% assign regular_posts = site.data.posts | where_exp: "post", "post.featured != true" %} + +
+

Community insights

+

Latest from our Blog

+

+ Insights, updates, and deep dives into the world of document databases and open-source innovation. +

+

Articles open on their original publisher sites in a new tab.

+
+ +
+ {% for post in featured_posts %} + {% include post-card.html post=post featured=true %} + {% endfor %} + + {% for post in regular_posts %} + {% include post-card.html post=post featured=false %} + {% endfor %} +
diff --git a/package.json b/package.json index 2c332ae..0dfcd18 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "private": true, "scripts": { "dev": "npm run compile && next dev --turbopack", - "build": "npm run compile && next build --turbopack", + "build": "npm run build:next && npm run build:blogs", + "build:next": "npm run compile && next build --turbopack", + "build:blogs": "BUNDLE_FORCE_RUBY_PLATFORM=true bundle exec jekyll build --config blogs/_config.yml --source blogs --destination out/blogs --baseurl \"${JEKYLL_BASE_PATH:-/blogs}\"", "compile": "npm run compile:clean && npm run compile:content", "compile:content": "tsx scripts/compile-content.tsx", "compile:clean": "tsx scripts/compile-clean.tsx", diff --git a/readme.md b/readme.md index 422c9e3..0b987bf 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,13 @@ # DocumentDB Website -A modern website for DocumentDB built with [Next.js](https://nextjs.org/) and [Tailwind CSS](https://tailwindcss.com/). The site features community blog posts and technical documentation, with content automatically pulled from the [documentdb/docs](https://github.com/documentdb/docs) repository during the build process. +A modern website for DocumentDB built with [Next.js](https://nextjs.org/) for the main site and [Jekyll](https://jekyllrb.com/) for the `/blogs/` section. The site features community blog posts and technical documentation, with content automatically pulled from the [documentdb/docs](https://github.com/documentdb/docs) repository during the build process. ## Prerequisites for Development - **[Node.js](https://nodejs.org/)** (*20 or higher*) +- **[Ruby](https://www.ruby-lang.org/)** with **Bundler** (*for the Jekyll-powered blog section*) + - **[Git](https://git-scm.com/)** (*for cloning documentation content*) You can develop locally on any machine with these prerequisites installed, or use [GitHub Codespaces](#develop-in-github-codespaces) for a pre-configured environment. @@ -22,13 +24,26 @@ Get started by cloning and running this repository locally. npm install ``` -1. Start the development server: +1. Install Ruby dependencies: + + ```bash + BUNDLE_FORCE_RUBY_PLATFORM=true bundle install + ``` + +1. Start the Next.js development server: ```bash npm run dev ``` -1. Observe that the site will be available at http://localhost:3000 +1. For a full static preview, including the Jekyll-powered blog section: + + ```bash + npm run build + npm run start + ``` + +1. Observe that the preview site will be available at http://localhost:3000 The first time you run `npm run dev` or `npm run build`, documentation content will be automatically compiled from the [documentdb/docs](https://github.com/documentdb/docs) repository. @@ -40,7 +55,7 @@ We welcome contributions to improve the DocumentDB website, whether it's blog po Blog posts are managed locally in this repository. Contribute directly through a pull request to this repository. -1. Open [blogs/content.yml](blogs/content.yml) +1. Open [blogs/_data/posts.yml](blogs/_data/posts.yml) 1. Add your blog post entry following the format of existing posts @@ -100,13 +115,13 @@ The `content.config.json` file controls how documentation is compiled from exter While content is automatically compiled during builds, you can manually trigger these operations during development: ```bash -# Clean all target directories -npm run clean:content +# Build the full static site artifact +npm run build ``` ```bash -# Compile content from sources -npm run compile:content +# Start the static preview server +npm run start ``` ## Develop in GitHub Codespaces From 3ee41e78d0a9ddd1a495ee87a15f54b7dd059577 Mon Sep 17 00:00:00 2001 From: Guanzhou Song Date: Thu, 19 Mar 2026 12:49:47 -0400 Subject: [PATCH 2/4] Add new blogpost --- blogs/_config.yml | 8 + blogs/_data/categories.yml | 3 + blogs/_includes/post-card.html | 50 +++- blogs/_layouts/post.html | 53 ++++ ...-19-meet-documentdb-kubernetes-operator.md | 91 ++++++ blogs/assets/blog.css | 277 +++++++++++++++++- blogs/index.html | 62 +++- readme.md | 44 ++- 8 files changed, 558 insertions(+), 30 deletions(-) create mode 100644 blogs/_layouts/post.html create mode 100644 blogs/_posts/2026-03-19-meet-documentdb-kubernetes-operator.md diff --git a/blogs/_config.yml b/blogs/_config.yml index 133e681..f9503d1 100644 --- a/blogs/_config.yml +++ b/blogs/_config.yml @@ -12,3 +12,11 @@ defaults: path: "" values: layout: default + - + scope: + path: "" + type: posts + values: + layout: post + category: documentdb-blog + permalink: /posts/:title/ diff --git a/blogs/_data/categories.yml b/blogs/_data/categories.yml index 682eb6f..65e72ca 100644 --- a/blogs/_data/categories.yml +++ b/blogs/_data/categories.yml @@ -1,3 +1,6 @@ +documentdb-blog: + label: DocumentDB Blog + meta: Published here microsoft-open-source-blog: label: Microsoft Open Source Blog meta: August 25, 2025 diff --git a/blogs/_includes/post-card.html b/blogs/_includes/post-card.html index 2db22c5..79ffd0f 100644 --- a/blogs/_includes/post-card.html +++ b/blogs/_includes/post-card.html @@ -1,25 +1,57 @@ {% assign post = include.post %} {% assign category = site.data.categories[post.category] %} +{% assign description = post.description | default: post.excerpt | strip_html | strip_newlines | strip %} + +{% if post.date %} + {% assign meta_text = post.date | date: "%B %-d, %Y" %} +{% elsif post.meta %} + {% assign meta_text = post.meta %} +{% else %} + {% assign meta_text = category.meta | default: "Community article" %} +{% endif %} + +{% if post.uri %} + {% assign href = post.uri %} + {% assign is_external = true %} +{% else %} + {% assign href = post.url | relative_url %} + {% assign is_external = false %} +{% endif %}
+ {% if post.cover_image %} + {% if post.cover_image contains '://' %} + {% assign cover_image_src = post.cover_image %} + {% else %} + {% assign cover_image_src = post.cover_image | relative_url %} + {% endif %} +
+ {{ post.cover_image_alt | default: post.title | escape }} +
+ {% endif %} +

{{ category.label | default: post.category }}

-

{{ category.meta | default: "Community article" }}

+

{{ meta_text }}

{{ post.title }}

-

{{ post.description }}

+

{{ description }}

{% if post.tags and post.tags.size > 0 %}
@@ -30,7 +62,11 @@

{{ post.title }}

{% endif %} - {% if include.featured %}Read full article{% else %}Read more{% endif %} + {% if is_external %} + {% if include.featured %}Read full article{% else %}Read more{% endif %} + {% else %} + {% if include.featured %}Read featured post{% else %}Read post{% endif %} + {% endif %}
diff --git a/blogs/_layouts/post.html b/blogs/_layouts/post.html new file mode 100644 index 0000000..13f702b --- /dev/null +++ b/blogs/_layouts/post.html @@ -0,0 +1,53 @@ +--- +layout: default +--- +{% assign category = site.data.categories[page.category] %} + +
+

+ ← Back to all blog posts +

+ +
+

{{ category.label | default: "DocumentDB Blog" }}

+

{{ page.title }}

+ + + + {% if page.description %} +

{{ page.description }}

+ {% endif %} + + {% if page.tags and page.tags.size > 0 %} + + {% endif %} +
+ + {% if page.cover_image %} + {% if page.cover_image contains '://' %} + {% assign cover_image_src = page.cover_image %} + {% else %} + {% assign cover_image_src = page.cover_image | relative_url %} + {% endif %} +
+ {{ page.cover_image_alt | default: page.title | escape }} +
+ {% endif %} + +
+
+ {{ content }} +
+
+
diff --git a/blogs/_posts/2026-03-19-meet-documentdb-kubernetes-operator.md b/blogs/_posts/2026-03-19-meet-documentdb-kubernetes-operator.md new file mode 100644 index 0000000..46aaf2a --- /dev/null +++ b/blogs/_posts/2026-03-19-meet-documentdb-kubernetes-operator.md @@ -0,0 +1,91 @@ +--- +title: Meet the DocumentDB Kubernetes Operator +description: Preview an open-source operator that brings declarative deployment, secure connectivity, high availability, backup and restore, and multi-environment deployment patterns to DocumentDB on Kubernetes. +date: 2026-03-19 +featured: true +author: DocumentDB team +category: documentdb-blog +tags: + - Kubernetes + - Operator + - DocumentDB + - Open Source +--- +Running a document database on Kubernetes should not require a maze of hand-built scripts for failover, certificate handling, backup jobs, and day-two troubleshooting. The new DocumentDB Kubernetes Operator is designed to turn that operational work into a Kubernetes-native workflow. + +DocumentDB is the engine powering vCore-based Azure Cosmos DB for MongoDB. Built on PostgreSQL, it provides a native document-oriented NoSQL database with support for CRUD operations on BSON data types. The Kubernetes Operator runs and manages DocumentDB on Kubernetes: when you deploy a cluster, it creates and manages PostgreSQL instances, the DocumentDB Gateway, and the supporting Kubernetes resources around them. Because the gateway enables MongoDB-compatible drivers, APIs, and tools, teams can keep working with familiar clients such as `mongosh`. + +## Why this matters + +Kubernetes teams have strong patterns for stateless applications, but data services often still depend on custom runbooks and fragile operational glue. The DocumentDB Kubernetes Operator closes that gap for DocumentDB by giving platform teams a single declarative control surface for deployment, availability, security, and recovery. + +With the preview release, you can: + +- install the operator with Helm +- have the Helm chart install CloudNativePG as a dependency +- deploy DocumentDB through a Kubernetes custom resource +- connect with `mongosh` and other MongoDB-compatible tooling +- manage backup, restore, TLS, and promotion workflows through Kubernetes-native APIs + +## From quickstart to useful data fast + +The quickstart is intentionally direct. Install `cert-manager`, install the operator, create a Secret for gateway credentials, and apply a `DocumentDB` resource. The public preview docs target Kubernetes 1.35+ and call out local development with `kind` (v0.31+) and `minikube`, while also showing cloud-friendly access patterns through `LoadBalancer` services on AKS, EKS, and GKE. + +At the heart of that flow is a simple custom resource: + +```yaml +apiVersion: documentdb.io/preview +kind: DocumentDB +metadata: + name: documentdb-preview + namespace: documentdb-preview-ns +spec: + nodeCount: 1 + instancesPerNode: 1 + documentDbCredentialSecret: documentdb-credentials + resource: + storage: + pvcSize: 10Gi + exposeViaService: + serviceType: ClusterIP +``` + +Once the cluster reports a healthy state, you can connect locally with port forwarding or expose it through a load balancer in supported environments. The result is a much shorter path from cluster creation to a live MongoDB-compatible endpoint. + +## Built for day-two operations + +Bringing up a cluster is only the beginning, so the operator is opinionated about day-two workflows as well. + +Set `instancesPerNode: 3` and the operator creates one primary instance and two replicas for local high availability and automatic failover. Use `Backup` and `ScheduledBackup` resources for on-demand and scheduled backups, retention policies, and restore workflows into a new cluster. And when operators need visibility into what is happening, the `kubectl documentdb` plugin adds purpose-built commands for status inspection, event triage, and primary promotion. + +That makes the operator interesting not only for first deployment, but for the operational rhythm that follows: health checks, recovery planning, planned changes, and repeatable workflows that fit naturally into Kubernetes. + +## Security and connectivity without reinventing the basics + +Secure connectivity is built into the model from the start. The DocumentDB gateway always encrypts client connections; the TLS mode controls how certificates are managed. In practice, that means teams can choose the workflow that matches their environment: + +- `SelfSigned` for development and test environments +- `CertManager` for clusters that already standardize on cert-manager +- `Provided` for organizations that manage certificates through their own PKI processes + +The same pattern carries into networking. The docs cover local development through `ClusterIP` plus port forwarding, and cloud exposure through `LoadBalancer` services where that model makes sense. The operator keeps those choices declarative instead of forcing each team to reinvent them. + +## A practical path to multi-environment deployment + +The public docs position the operator across multiple cloud environments and Kubernetes distributions, and the multi-cluster guidance goes further with a documented KubeFleet-based deployment pattern spanning AKS and an on-premises Kubernetes cluster. That is a meaningful direction for teams that want Kubernetes-native control over data placement and replication without committing their operational model to a single environment. + +Just as importantly, the operator keeps sensitive workflows explicit. Promotion is an intentional operational action, surfaced through Kubernetes resources and the `kubectl documentdb promote` workflow, which is often exactly the right design when changes cross infrastructure or compliance boundaries. + +## Why try it now + +The project is still in preview, and the public docs are clear that it is not yet recommended for production workloads. That is exactly why now is the right time to evaluate it. + +If you want to see how DocumentDB could fit into your Kubernetes platform, this preview gives you a concrete way to do it: start locally, inspect the control model, test backup and restore, evaluate TLS options, and explore the operator experience before general availability. + +## Start exploring + +- [Quickstart guide](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/operator-public-documentation/preview/index.md) +- [Backup and restore guide](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/operator-public-documentation/preview/backup-and-restore.md) +- [kubectl-documentdb plugin](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/operator-public-documentation/preview/kubectl-plugin.md) +- [TLS configuration guide](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/operator-public-documentation/preview/configuration/tls.md) +- [Multi-cluster deployment guide](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/developer-guides/manual-multi-cloud-deployment-guide.md) diff --git a/blogs/assets/blog.css b/blogs/assets/blog.css index 0827f49..5c3fd5c 100644 --- a/blogs/assets/blog.css +++ b/blogs/assets/blog.css @@ -94,7 +94,9 @@ a { } .blog-hero, -.posts-list { +.posts-group, +.posts-list, +.blog-post { width: min(1100px, 100%); margin: 0 auto; } @@ -128,15 +130,25 @@ a { color: var(--muted); } -.blog-hero__note { - display: inline-flex; - margin-top: 1.6rem; - padding: 0.6rem 1rem; - border-radius: 999px; - border: 1px solid rgba(96, 165, 250, 0.25); - background: rgba(59, 130, 246, 0.08); - color: #cbd5e1; - font-size: 0.95rem; +.posts-group { + margin-bottom: 3rem; +} + +.posts-group__header { + margin-bottom: 1.1rem; +} + +.posts-group__title { + margin: 0; + font-size: clamp(1.45rem, 2.2vw, 1.9rem); + letter-spacing: -0.03em; +} + +.posts-group__description { + margin: 0.45rem 0 0; + max-width: 48rem; + color: var(--muted); + line-height: 1.7; } .posts-list { @@ -189,6 +201,25 @@ a { padding: 1.75rem; } +.post-card__media { + position: relative; + aspect-ratio: 16 / 9; + overflow: hidden; + border-bottom: 1px solid rgba(148, 163, 184, 0.14); + background: rgba(15, 23, 42, 0.8); +} + +.post-card__image { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} + +.post-card--featured .post-card__media { + aspect-ratio: 16 / 8; +} + .post-card--featured .post-card__body { padding: 2.3rem; } @@ -307,6 +338,227 @@ a { --accent-alt-rgb: 16, 185, 129; } +.post-card--documentdb-blog { + --accent-rgb: 14, 165, 233; + --accent-alt-rgb: 59, 130, 246; +} + +.blog-post { + max-width: 840px; +} + +.blog-post__back { + margin: 0 0 1.2rem; +} + +.blog-post__back a { + color: var(--muted); + font-weight: 600; + transition: color 160ms ease; +} + +.blog-post__back a:hover { + color: var(--text); +} + +.blog-post__header, +.blog-post__content-shell { + border: 1px solid var(--border); + border-radius: 28px; + background: linear-gradient(180deg, rgba(17, 24, 39, 0.82), rgba(10, 10, 11, 0.9)); + box-shadow: 0 24px 60px -28px var(--shadow); +} + +.blog-post__header { + padding: 2rem; +} + +.blog-post__eyebrow { + margin: 0; + font-size: 0.92rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #7dd3fc; +} + +.blog-post__title { + margin: 0.75rem 0 0; + font-size: clamp(2.4rem, 5vw, 3.8rem); + line-height: 1.04; + letter-spacing: -0.05em; +} + +.blog-post__meta { + display: flex; + flex-wrap: wrap; + gap: 0.85rem; + margin-top: 1rem; + color: var(--subtle); + font-size: 0.95rem; +} + +.blog-post__lead { + margin: 1rem 0 0; + color: var(--muted); + font-size: 1.08rem; + line-height: 1.75; +} + +.blog-post__tags { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + margin-top: 1.35rem; +} + +.blog-post__tag { + display: inline-flex; + align-items: center; + padding: 0.45rem 0.8rem; + border-radius: 999px; + background: rgba(14, 165, 233, 0.14); + color: #7dd3fc; + font-size: 0.8rem; + font-weight: 600; +} + +.blog-post__content-shell { + margin-top: 1.5rem; + padding: 2rem; +} + +.blog-post__hero { + margin-top: 1.5rem; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 28px; + background: rgba(15, 23, 42, 0.85); + box-shadow: 0 24px 60px -28px var(--shadow); +} + +.blog-post__hero-image { + display: block; + width: 100%; + height: auto; +} + +.blog-post__content { + color: #e5e7eb; + line-height: 1.8; + font-size: 1.05rem; +} + +.blog-post__content > :first-child { + margin-top: 0; +} + +.blog-post__content > :last-child { + margin-bottom: 0; +} + +.blog-post__content h2, +.blog-post__content h3, +.blog-post__content h4 { + margin-top: 2.25rem; + margin-bottom: 0.8rem; + line-height: 1.2; + letter-spacing: -0.03em; +} + +.blog-post__content h2 { + font-size: 1.75rem; +} + +.blog-post__content h3 { + font-size: 1.35rem; +} + +.blog-post__content p, +.blog-post__content ul, +.blog-post__content ol, +.blog-post__content blockquote, +.blog-post__content pre, +.blog-post__content table { + margin: 1.2rem 0; +} + +.blog-post__content ul, +.blog-post__content ol { + padding-left: 1.5rem; +} + +.blog-post__content li + li { + margin-top: 0.45rem; +} + +.blog-post__content a { + color: #93c5fd; + text-decoration: underline; + text-decoration-color: rgba(147, 197, 253, 0.35); + text-underline-offset: 0.18em; +} + +.blog-post__content a:hover { + color: #bfdbfe; +} + +.blog-post__content blockquote { + padding: 1rem 1.25rem; + border-left: 4px solid rgba(59, 130, 246, 0.7); + border-radius: 16px; + background: rgba(30, 41, 59, 0.55); + color: #dbeafe; +} + +.blog-post__content pre { + overflow-x: auto; + padding: 1rem 1.15rem; + border-radius: 18px; + border: 1px solid rgba(148, 163, 184, 0.16); + background: #0f172a; +} + +.blog-post__content code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 0.92em; +} + +.blog-post__content :not(pre) > code { + padding: 0.18rem 0.42rem; + border-radius: 10px; + background: rgba(30, 41, 59, 0.75); +} + +.blog-post__content img { + display: block; + width: 100%; + max-width: 100%; + margin: 1.5rem 0; + border: 1px solid rgba(148, 163, 184, 0.16); + border-radius: 24px; + background: rgba(15, 23, 42, 0.82); + box-shadow: 0 24px 60px -28px var(--shadow); +} + +.blog-post__content table { + width: 100%; + border-collapse: collapse; +} + +.blog-post__content th, +.blog-post__content td { + padding: 0.75rem; + border-bottom: 1px solid rgba(148, 163, 184, 0.16); + text-align: left; +} + +.blog-post__content hr { + margin: 2rem 0; + border: 0; + border-top: 1px solid rgba(148, 163, 184, 0.16); +} + .site-footer { border-top: 1px solid var(--border); background: rgba(9, 9, 11, 0.9); @@ -333,6 +585,11 @@ a { .blog-main { padding-top: 3rem; } + + .blog-post__header, + .blog-post__content-shell { + padding: 1.5rem; + } } @media (max-width: 640px) { diff --git a/blogs/index.html b/blogs/index.html index 34c3e8c..01d3efb 100644 --- a/blogs/index.html +++ b/blogs/index.html @@ -2,8 +2,10 @@ title: Latest from our Blog description: Insights, updates, and deep dives into the world of document databases and open-source innovation. --- -{% assign featured_posts = site.data.posts | where: "featured", true %} -{% assign regular_posts = site.data.posts | where_exp: "post", "post.featured != true" %} +{% assign featured_local_posts = site.posts | where: "featured", true %} +{% assign featured_external_posts = site.data.posts | where: "featured", true %} +{% assign local_posts = site.posts | where_exp: "post", "post.featured != true" %} +{% assign external_posts = site.data.posts | where_exp: "post", "post.featured != true" %}

Community insights

@@ -11,15 +13,53 @@

Latest from our Blog

Insights, updates, and deep dives into the world of document databases and open-source innovation.

-

Articles open on their original publisher sites in a new tab.

-
- {% for post in featured_posts %} - {% include post-card.html post=post featured=true %} - {% endfor %} +{% if featured_local_posts.size > 0 or featured_external_posts.size > 0 %} +
+
+ +

Highlighted posts and important community announcements.

+
- {% for post in regular_posts %} - {% include post-card.html post=post featured=false %} - {% endfor %} -
+
+ {% for post in featured_local_posts %} + {% include post-card.html post=post featured=true %} + {% endfor %} + + {% for post in featured_external_posts %} + {% include post-card.html post=post featured=true %} + {% endfor %} +
+
+{% endif %} + +{% if local_posts.size > 0 %} +
+
+

Published here

+

Markdown posts maintained directly in this repository and rendered with the same card style as the rest of the blog.

+
+ +
+ {% for post in local_posts %} + {% include post-card.html post=post featured=false %} + {% endfor %} +
+
+{% endif %} + +{% if external_posts.size > 0 %} +
+
+

From around the web

+

Curated partner and community articles hosted outside this repository.

+
+ +
+ {% for post in external_posts %} + {% include post-card.html post=post featured=false %} + {% endfor %} +
+
+{% endif %} diff --git a/readme.md b/readme.md index 0b987bf..46e9c79 100644 --- a/readme.md +++ b/readme.md @@ -55,9 +55,49 @@ We welcome contributions to improve the DocumentDB website, whether it's blog po Blog posts are managed locally in this repository. Contribute directly through a pull request to this repository. -1. Open [blogs/_data/posts.yml](blogs/_data/posts.yml) +You can publish blog content in two ways: -1. Add your blog post entry following the format of existing posts +1. **Markdown posts hosted in this repo** + + Add a new file under `blogs/_posts/` using the Jekyll naming format: + + ```text + blogs/_posts/YYYY-MM-DD-my-post-title.md + ``` + + Start it with front matter like: + + ```yaml + --- + title: My Blog Post + description: One-line summary used in the blog card. + date: 2026-03-19 + featured: false + author: Your Name + category: documentdb-blog + cover_image: /assets/images/posts/my-post-title/hero.png + cover_image_alt: Short description of the image + tags: + - Example + - Markdown + --- + ``` + + Store post images under: + + ```text + blogs/assets/images/posts/my-post-title/ + ``` + + Then write the post body in Markdown. Jekyll renders the post page and uses the same card layout on the blog index. Images can be referenced directly from Markdown, for example: + + ```md + ![Architecture diagram]({{ '/assets/images/posts/my-post-title/diagram.png' | relative_url }}) + ``` + +1. **Curated external articles** + + Open [blogs/_data/posts.yml](blogs/_data/posts.yml) and add a new entry following the existing YAML format when you want the card to link to an article hosted elsewhere. 1. Submit a pull request for review From 5f060ddb4f6e4a5f5fb4c02284244efbdd5d7f57 Mon Sep 17 00:00:00 2001 From: Guanzhou Song Date: Thu, 19 Mar 2026 13:07:12 -0400 Subject: [PATCH 3/4] Fix CI for Jekyll blog build Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/continuous-integration.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ee09c25..72ef7b5 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -41,6 +41,12 @@ jobs: with: node-version: 24 cache: npm + - name: Setup Ruby + # The full site build now includes the Jekyll-powered blogs section. + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3 + bundler-cache: true - name: Install dependencies run: npm ci - name: Build Next.js site From 959c75cc8f57b58552f81d1094a0c8308d8d32a1 Mon Sep 17 00:00:00 2001 From: Guanzhou Song Date: Thu, 19 Mar 2026 13:13:09 -0400 Subject: [PATCH 4/4] Update Bundler lockfile for Ruby 3.3 CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 91f2cd8..acc265d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,4 +73,4 @@ DEPENDENCIES webrick (~> 1.8) BUNDLED WITH - 1.17.2 + 2.4.22