慣性聚合 高效追讀感興趣之博客、新聞、科技資訊
閱原文 以慣性聚合開啟

推薦訂閱源

Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
博客园 - 叶小钗
MyScale Blog
MyScale Blog
V
Visual Studio Blog
月光博客
月光博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
I
InfoQ
有赞技术团队
有赞技术团队
阮一峰的网络日志
阮一峰的网络日志
Jina AI
Jina AI
V
V2EX
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
Last Week in AI
Last Week in AI
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
博客园 - Franky

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python)
构建博客平台之五:增 Dockerfile + 部署至 Clouderized
David Tio · 2026-05-24 · via DEV Community

🐳 以 Docker 筑博客平台 #5:增 Dockerfile + 部署至 Clouderized

速记一句:撰 Dockerfile,建镜像,以一git push部署 Clouderized,使 Flask 博客得活,自动 HTTPS,无服务器之劳形.


🔧 既欲始,先论 Blogger 迁徙之 URL 控制

触 Docker 之前,有四则之修,今可为之。

今吾之平台,自文件名构 URL:hey-markdown.md化而为/2026/04/hey-markdown.html。此法适新文,然不契移自 Blogger 之 URL:

Blogger URL 基于文件名之 URL
/2026/03/docker-rootless-on-ubuntu-2026-guide.html /2026/03/docker-rootless-ubuntu.html

吾于前文加之canonical_url,用以二事:

  1. 此平台实际供文之URL也— 迁徙之文,仍守其 Blogger 之径。
  2. <link rel="canonical">—告Google此页之内容与旧Blogger URL无异

更新content/posts/hey-markdown.md

---
title: "Hey Markdown"
date: 2026-04-25
description: "The first post written in Markdown  no more writing HTML by hand."
tags: [meta, blog]
canonical_url: "https://blog.dtio.app/2026/04/old-markdown.html"
---

入全景模式 出全屏模式

此不协乃有意为之:文件名与所供路径可异。

葆助URL之器于 app.py

app.py。頂部添此引:

from urllib.parse import urlparse

入全屏狀態 離全屏狀態

次於 get_post_path() 之後添此函:parse_post()

def get_post_path(meta):
    """Determine the URL path for a post from its canonical_url."""
    if meta.get('canonical_url'):
        return urlparse(meta['canonical_url']).path
    # Fallback: auto-generate from date + slug
    slug = meta.get('slug', 'unknown')
    date = meta.get('date', '')
    if hasattr(date, 'year'):
        return f'/{date.year}/{date.month:02d}/{slug}.html'
    return f'/{slug}.html'

入全屏狀態 離全屏狀態

此助器取URL之徑於canonical_url,兼以日期+缩略名为备。

更新get_all_posts()

于既有get_all_posts()之函数,增路径之域:

def get_all_posts():
    posts = []
    posts_dir = 'content/posts'
    for filename in os.listdir(posts_dir):
        if not filename.endswith('.md'):
            continue
        filepath = os.path.join(posts_dir, filename)
        post = parse_post(filepath)
        post['slug'] = filename[:-3]
        post['path'] = get_post_path(post)
        posts.append(post)
    posts.sort(key=lambda p: p.get('date', ''), reverse=True)
    return posts

切换全屏模式 退出全屏模式

每篇文皆具path之域——即其应服务之确URL。

更新路由

于既有路由中寻之app.py

@app.route('/<int:year>/<int:month>/<slug>')
def post(year, month, slug):
    filepath = f'content/posts/{slug}.md'
    if not os.path.exists(filepath):
        abort(404)
    post = parse_post(filepath)
    return render_template('post.html', post=post)

进入全屏模式 退出全屏模式

以动态查询替换之:

@app.route('/<int:year>/<int:month>/<path:slug>.html')
def post(year, month, slug):
    target_path = f'/{year}/{month:02d}/{slug}.html'
    all_posts = get_all_posts()
    matching_post = next((p for p in all_posts if p['path'] == target_path), None)

    if not matching_post:
        abort(404)

    post_data = parse_post(f'content/posts/{matching_post["slug"]}.md')
    post_data['path'] = matching_post['path']
    return render_template('post.html', post=post_data)

进入全屏模式 退出全屏模式

此路径匹配.html网址,寻得相应post['path'],载入正确之markdown文件.

更新首页链接

templates/index.html,更易手所构之URL,以帖之径代之。

<a href="{{ post.path }}">{{ post.title }}</a>

入全景模式 出全屏模式

且于"Read more"之链:

<a href="{{ post.path }}" class="text-teal-400 text-sm hover:text-teal-300 font-medium transition-colors duration-200">
    Read more →
</a>

入全景模式 出全屏模式

增置规范链接标签

templates/post.html,加诸规范链接之标籤于内<head>,元描述之后:

<meta name="description" content="{{ post.description }}">
{% if post.canonical_url %}
<link rel="canonical" href="{{ post.canonical_url }}">
{% endif %}

切换全屏模式 退出全屏模式

{% if %}之卫,使标签于无canonical_url之文可略。

更新前文架构

此乃今后完整之前文架构:

字段 用于
title <title>之標籤,<h1>於帖子頁,帖子列表
date URL生成,排序,顯示
description <meta name="description">,摘錄於首頁
tags Tag藥丸於帖子頁,終究標籤頁於Ep12
canonical_url 首頁鏈接+<link rel="canonical">為Blogger遷移(可選)

遷移帖子時,設canonical_url以合原BloggerURL。


✅ 第一步:验诸事于本地

未及触碰Docker,先验平台URL更易后,犹能运行无碍否

激活汝之venv,运此应用:

$ source venv/bin/activate
$ python app.py

进入全屏模式 退出全屏模式

访 http://localhost:8000 并察:

  • 首页 — 文章已列,链接指向正确路径
  • 文章页面 — 渲染无误,<link rel="canonical">见于HTML源码(检视源码并搜索canonical
  • 标签丸 — 文章页正确显示

一旦本地皆妥,即可容器化之。


次步:立.dockerignore

先撰Dockerfile,而后示Docker何者不宜入图。于项目根目录立.dockerignore:.dockerignore 全屏模式

venv/
__pycache__/
*.pyc
.git/
.gitignore

退出全屏模式 此举使图像精简,免载本地环境之文件.


📄 次步:撰Dockerfile

Dockerfile 在项目根目录下:

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["python", "app.py"]

全屏模式进入 全屏模式退出

此序为何要重:

  • 先拷贝 requirements.txt 以便依赖安装可缓存
  • 后拷贝应用代码 使常规代码修改能速重建

🔨 步骤四:构建图像

务必处于项目根目录(即此处)Dockerfile(是)

$ docker build -t tioblog .

入全景模式 出全屏模式

既毕,验其图存否。

$ docker images tioblog
IMAGE            ID             DISK USAGE   CONTENT SIZE   EXTRA
tioblog:latest   05dbc52b9852        231MB         55.6MB

入全景模式 出全屏模式


🚀 第五步:运行之

$ docker run -p 8000:8000 tioblog

入全景模式 出全屏模式

JHSNS_URL_0。博客载入——与往昔无异,然今运行于器中.


📝 步第六:增置.gitignore

推于Clouderized之前,当慎勿使非属库者入于其中。于项目根处,立.gitignore

venv/
__pycache__/
*.pyc
.env

全屏模式 退出全屏模式

此令汝之虚拟境、缓存之文、及一切本地之配置,不存于版本控制之列.


🌐 步第七:部署于Clouderized

Clouderized乃此系列之部署目标,盖因其优化于简易容器应用之输送:推于Git,自建,自部署,HTTPS默认开启.

此博客之部署,如是:

$ git init
$ git add .
$ git commit -m "Initial commit for tioblog"
$ git branch -M main
$ git remote add origin https://git.clouderized.com/davidtio/tioblog.git
$ git push -u origin main

入全屏模式 退出全屏模式

https://git.clouderized.com/davidtio/tioblog.gitdavidtio为云端化用户名,以tioblog为项目名
于汝之应用,用:

https://git.clouderized.com/<username>/<project>.git

进入全屏模式 退出全屏模式

经一时辰,汝之博客即成于:

https://davidtio-tioblog.clouderized.com

进入全屏模式 退出全屏模式

同 Dockerfile,今以 HTTPS 公开运行。

添汝自定域名

Clouderized 亦支持自定域名。此博客,生产运行于 blog.dtio.app

  1. https://dash.clouderized.com
  2. tioblog 应用卡
  3. Domains
  4. 添汝自定域名(例如 blog.dtio.app
  5. 复制 cfargotunnel 所示之目标,由 Clouderized
  6. 为汝域创建 CNAME 记录,并指向 cfargotunnel 目标

Clouderized Domains view for tioblog custom domain setup

待 DNS 传播既,汝之应用即于自域运行,而 Clouderized 仍主路由与 HTTPS 之事.

小创作者平台所系何在?

  • 尔恒持一部署单元(汝之Docker图像)
  • 尔可避手维之逆代理与证书之设
  • 尔可同Git之工法运内容与应用之变

🧪 步第八:验其鲜活

启尔之瀏览器,验其活URL:

https://davidtio-tioblog.clouderized.com

入全屏模式 出全屏模式

请确认首页载入,文章已列,且.html链接地址可用。

https://davidtio-tioblog.clouderized.com/2026/04/hey-markdown.html

入全景模式 出全屏模式

此合Blogger式URL,以利迁移之顺。

亦验HTTPS之用,且证其牒之效。于Clouderized,此乃自为之,汝可专力於内容与产品之事,而不必忧於基设之劳。


所建何物

tiohub-blog/
├── Dockerfile         ← new
├── .dockerignore      ← new
├── app.py             (get_post_path, post['path'] in get_all_posts, route adds post['path'])
├── requirements.txt
├── static/
│   └── js/
│       ├── tailwind.config.js
│       └── code-blocks.js
├── templates/
│   ├── index.html     (links use {{ post.path }} instead of manual URLs)
│   └── post.html      (<link rel="canonical"> added)
└── content/
    └── posts/
        ├── hey-markdown.md  (canonical_url added to frontmatter)
        └── second-post.md

全屏模式入 全屏模式出

所变何在:

  • canonical_urlpost.path今主稳定之後URL
  • 動態之路解URL之徑至合適之markdown文
  • 首頁連結用{{ post.path }}
  • 可選之典標籤加post.html
  • Dockerfile.dockerignore.gitignore
  • ,該應用於容器內運行,需docker builddocker run
  • 部署至雲端,以供公眾HTTPS主機服務。
  • 自定義域名可於Domains中對應,DNS指向所供cfargotunnel目標。
  • 之部署,本于Git,故发布之务可复,以应将来系列之文

🚀 将至

一弊:每易一文,必重筑其像,乃见其变。是适得其反,无以彰内容之旨

次章:绑定挂载。吾等将挂载之content/ 之文件夹,直纳于容器——更文,刷新页,立见变。无需重构建。


此法有益乎? 分享于众,或留言于下。


搜索引擎元数据

  • 标题: 以Docker构建博客平台 #5:增Dockerfile + 部署于Clouderized
  • 元描述: 为汝之 Flask 博客撰 Dockerfile,建其像,而布之於 clouderized.com — 推至 git.clouderized.com/davidtio/tioblog,自建,自导,自 HTTPS,於 davidtio-tioblog.clouderized.com。