慣性聚合 関心のあるブログ、ニュース、テクノロジーを効率的に追跡
原文を読む 慣性聚合で開く

おすすめ購読元

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)
Dockerを使ったブログプラットフォームの構築 #5: Dockerfileを追加 + Clouderizedにデプロイ
David Tio · 2026-05-24 · via DEV Community

🐳 Dockerを使ってブログプラットフォームを構築する #5: Dockerfileを追加 + Clouderizedにデプロイ

簡単な一文で: Dockerfileを書き、イメージをビルドし、git push 1つでClouderizedにデプロイして、Flaskブログが自動HTTPSでライブになるようにし、サーバーの世話なしにする.


🔧 はじめに: Blogger移行のためのURL制御

Dockerに触れる前に、エピソード4の修正点の一つを今すぐ行う価値があります。

現在、私たちのプラットフォームはファイル名から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"> — このページが古いBlogger URLと同じコンテンツであることをGoogleに伝えます

更新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"
---

フルスクリーンモードを開始します フルスクリーンモードを終了します

この不一致は意図的です:ファイル名と提供されるパスは異なることがあります。

app.py

にURLヘルパーを追加してください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のURLに一致し、一致するpost['path']を見つけ、正しいマークダウンファイルをロードします.

ホームページのリンクを更新

インtemplates/index.html, 手動で構築したURLを投稿のパスに置き換える:

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

フルスクリーンモードに入る フルスクリーンモードを終了する

さらに「もっと読む」リンクについて:

<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 %}

フルスクリーンモードを開始 フルスクリーンモードを終了

The {% if %}ガードは、canonical_urlがない投稿においてタグを省略可能に保ちます.

Frontmatterスキーマの更新

これがこれからの完全なFrontmatterスキーマです:

フィールド 使用目的:
title <title> タグ、<h1> 投稿ページ上、投稿リスト
date URL生成、並び替え、表示
description <meta name="description">、ホームページ上の要約
tags 投稿ページ上のタグピル、最終的にEp12でタグページを設定
canonical_url ホームページリンク+<link rel="canonical">Blogger移行用(任意)

投稿を移行する際、canonical_urlを元のBloggerURLに合わせて設定。


✅ ステップ1: ローカルですべてが正常に動作することを確認

Dockerに触れる前に、URLが変更された後もプラットフォームが正しく動作することを確認してください

仮想環境をアクティブにしてアプリを実行します

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

フルスクリーンモードに入る フルスクリーンモードから抜ける

http://localhost:8000を訪れて確認してください

  • ホームページ — 投稿がリストされ、リンクは正しいパスに指しています
  • 投稿ページ — 正しくレンダリングされ、<link rel="canonical">はHTMLソースに存在します(ソースを表示してcanonicalを検索してください)
  • タグピルス — 投稿ページで正しく表示されます

ローカルですべてが動作したら、コンテナ化できる準備ができました


ステップ 2: .dockerignore を追加

Dockerfile を書く前に、Docker にイメージから除外すべきものを教えてください。プロジェクトのルートに .dockerignore を作成します。

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

フルスクリーンモードを開始 フルスクリーンモードを終了

これにより、イメージがスリムになり、ローカル環境のファイルが含まれないようになります。


📄 ステップ 3: 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 をコピーするので、依存関係のインストールがキャッシュされる
  • その後アプリのコードをコピーするので、通常のコード編集が迅速に再構築される

🔨 ステップ4:イメージをビルドする

プロジェクトのルートディレクトリにいることを確認してください(Dockerfileがある場所):

$ docker build -t tioblog .

フルスクリーンモードに入る フルスクリーンモードから抜ける

完了したら、画像が存在することを確認してください:

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

フルスクリーンモードに入る フルスクリーンモードから抜ける


🚀 ステップ5:実行する

$ docker run -p 8000:8000 tioblog

フルスクリーンモードに入る フルスクリーンモードから抜ける

にアクセスしてくださいhttp://localhost:8000です。ブログが読み込まれます—以前と同じですが、今はコンテナ内で実行されています


。 📝 ステップ6: .gitignoreを追加

Clouderizedにプッシュする前に、リポジトリに属さないものをコミットしていないことを確認してください。プロジェクトのルートに.gitignoreを作成します

venv/
__pycache__/
*.pyc
.env

フルスクリーンモードを開始 フルスクリーンモードを終了

この設定は、仮想環境、キャッシュファイル、およびローカル設定をバージョン管理から除外します。


🌐 ステップ7: 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

フルスクリーンモードに入る フルスクリーンモードを終了

1分から2分後、あなたのブログはこちらで公開されます:

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. Clouderizedによって表示されるcfargotunnelのターゲットをコピーします
  6. ドメイン用にCNAMEレコードを作成し、それをcfargotunnelのターゲットに指針を立てます

Clouderized Domains view for tioblog custom domain setup

DNSのプロパゲーションが完了した後、あなたのアプリケーションはあなたのドメインで提供され、Clouderizedは引き続きルーティングとHTTPSの処理を担当します

小規模クリエイタープラットフォームにとってこれが重要な理由は:

  • 一つのデプロイ単位(あなたのDockerイメージ)を保持します
  • 手動で行うリバースプロキシと証明書設定を回避します
  • 同じGitワークフローでコンテンツとアプリの変更を送信できます

🧪 ステップ8: ライブかどうかを確認します

ブラウザを開いてライブURLを確認します

https://davidtio-tioblog.clouderized.com

フルスクリーンモードに入ります フルスクリーンモードを抜けます

ホームページが読み込まれ、投稿がリスト化され、.htmlの投稿URLが機能することを確認してください:

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_url および post.path が安定した投稿URLを生成するようになりました
  • 动的なルートはURLパスを適切なマークダウンファイルに解決します
  • ホームページリンクは {{ post.path }}
  • にオプションのカノニカルタグが追加されました post.html
  • Dockerfile.dockerignore.gitignore
  • アプリをコンテナ内で実行し、docker build + docker run
  • のデプロイメントが Clouderized で公開 HTTPS ホスティングにプッシュされました。
  • カスタムドメインは Domains にマッピングでき、DNS は提供された cfargotunnel ターゲットを指します。
  • のデプロイフローはGitネイティブで、今後のシリーズ投稿のための公開操作を再現可能に保ちます

🚀 今後の予定

一つの問題:投稿を編集するたびに、変更を確認するためにイメージを再構築する必要があります。それはコンテンツ駆動型ブログの目的を無にします

次のエピソード:バインドマウント。私たちはマウントしますcontent/ フォルダを直接あなたのマシンからコンテナにドロップする——投稿を編集し、ページをリフレッシュし、すぐに変更を確認する。再ビルドは不要です.


これが役に立ったですか?ネットワークで共有するか、下のコメント欄にどうぞ.


SEO メタデータ

  • タイトル: Dockerでブログプラットフォームを作る #5: Dockerfileを追加 + Clouderizedにデプロイ
  • メタディスクリプション: Flaskのブログ用Dockerfileを作成し、イメージをビルドしてclouderized.comにデプロイします—git.clouderized.com/davidtio/tioblogにプッシュし、自動ビルド、自動ルーティング、自動HTTPSでdavidtio-tioblog.clouderized.comにデプロイします。