TECH MEMO / FIELD NOTEGitHubプロフィールを「技術者の名刺」にする。
はじめに
自分のGitHubプロフィールを見直したとき、Contribution GraphやPinned repositoriesだけでは、私が普段どの領域に関心を持っているのかまでは伝わりにくいと感じました。
経歴、扱っている技術、執筆記事、趣味は、すでに個人サイトへまとめています。同じ内容をGitHubにも載せると、プロフィールが長くなるうえ、経歴を更新するたびに2か所を直さなければなりません。そこで、個人サイトを詳細版、GitHubプロフィールを要約版と分けることにしました。
GitHubでは、次の情報へ短時間でたどり着ける「技術者の名刺」を目指しました。
- 何をしているエンジニアなのか
- どの技術を扱っているのか
- 公開リポジトリでは、どの言語を使っているのか
- 代表的な成果物は何か
- 詳しいプロフィールや技術記事をどこで読めるのか
この記事では、READMEの構成を決めた過程と、GitHub上で表示を確認しながら足したもの、反対に削ったものをまとめます。GitHub Actionsによる自動更新とブランチ保護を併用したときの判断も残します。
GitHub Profile READMEを表示するSpecial Repository
GitHubでは、自分のユーザー名と同じ名前のPublicリポジトリを作り、そのルートにREADME.mdを置くと、内容がプロフィールページに表示されます。
私のユーザー名はurchin-hatなので、作成したリポジトリは次のとおりです。
GitHubプロフィールREADMEを管理するSpecial Repository。
公開リポジトリとして作成しているため、実際のREADMEの記述やファイル配置、コミット履歴なども直接確認できます。
GitHub公式ドキュメントでは、Profile READMEが表示される条件として、次の4点が案内されています。
- リポジトリ名がユーザー名と一致している
- Publicリポジトリである
- ルートに
README.mdがある README.mdが空ではない
詳しい要件は、Managing your profile READMEを参照してください。
作成直後のREADMEには、Hi thereから始まるコメント付きテンプレートが入っています。今回はテンプレートを削除し、構成を一から作りました。
個人サイトとの使い分け:プロフィールは動線に徹する
最初に決めたのは、READMEへ何を書くかではなく、個人サイトとGitHubのどちらに何を置くかです。私の場合、職歴や技術記事はすでに個人サイトへまとめていたため、GitHubプロフィールにはその入口だけを置くことにしました。
| 比較軸 | 個人サイト (urchin-hat.org) | GitHubプロフィール |
|---|---|---|
| 役割 | 詳細版・ストック | 要約版・動線(インデックス) |
| 主な来訪動機 | 筆者自身に関心があり、経歴や考え方を深く知りたい | リポジトリやPRから訪れ、「どんな技術者か」を素早く知りたい |
| 掲載内容 | 職歴、価値観、技術記事(Memo)、趣味など網羅的 | 肩書き、関心領域、主要技術、代表リポジトリなど最小限 |
| 更新のしやすさ | 情報を一元管理する | 二重管理を避け、変更頻度を抑える |
GitHubプロフィールは、公開リポジトリやPR、Issueで私のアイコンを見た人が開く場面を想定しました。長い職歴をもう一度載せるより、「何をしているエンジニアか」「どの技術を扱っているか」が先に見えるほうが、GitHub上では役に立つと考えました。
READMEを作り込む途中では、紹介したいものを足すほど縦に長くなり、下にあるPinned repositoriesが遠くなりました。詳細な経歴まで書けば、個人サイトとの二重管理にもなります。表示してみて初めて、情報を増やすことより、役割の重複をなくすことが必要だと分かりました。
そこでGitHubプロフィールは、興味を持った人が個人サイトや公開リポジトリへ進むための動線と位置づけ、READMEは1画面程度の構成へ絞りました。
Hero / About
↓
Skills
↓
Languages
当初はFeatured Projects(代表リポジトリカード)やLatest Posts(最新記事一覧)も作りました。しかし、GitHub標準のPinned repositoriesや、上部に置いた外部リンクと役割が重なります。実際の画面を見て密度が高いと感じたため、最終版からはどちらも外しました。
Hero:肩書き、キャラクター、外部リンクをまとめる
READMEの先頭は、名前、肩書き、キャラクターGIF、外部リンクに絞りました。
<div align="center">
# Hi, I'm urchin_hat 👋
**SRE / Infrastructure Engineer**
<img src="./urchin-hat.gif" width="320" alt="焚き火のそばに座る、ウニ帽子をかぶったキャラクター" />
<!-- 外部リンクのバッジ -->
</div>
キャラクターGIFを置く
プロフィールのリポジトリへ、384×384ピクセル、約244KBのurchin-hat.gifを配置しました。READMEから相対パスで参照し、表示幅は320ピクセルにしています。
元画像より大きく引き伸ばさず、ドット絵の見え方を保てる大きさにしました。altには「画像」とだけ書かず、焚き火のそばに座るキャラクターであることを記述しています。
Shields.ioでリンクバッジを作る
外部リンクには、Shields.ioのStatic Badgeを使いました。技術スタックまでバッジにすると役割が混ざるため、Shields.ioは個人サイトやSNSへ移動するためのリンクに限っています。技術スタックは後述するSkill Iconsへ分けました。
<p>
<a href="https://urchin-hat.org/"><img src="https://img.shields.io/badge/Website-urchin--hat.org-1f2937?style=flat-square&logo=googlechrome&logoColor=white" alt="Website" /></a>
<a href="https://urchin-hat.org/memo/"><img src="https://img.shields.io/badge/Tech_Memo-Read-1f2937?style=flat-square&logo=hugo&logoColor=white" alt="Tech Memo" /></a>
<a href="https://www.facebook.com/294fk?locale=ja_JP"><img src="https://img.shields.io/badge/Facebook-Connect-0866FF?style=flat-square&logo=facebook&logoColor=white" alt="Facebook" /></a>
<a href="https://x.com/urchin_hat"><img src="https://img.shields.io/badge/X-Follow-000000?style=flat-square&logo=x&logoColor=white" alt="X" /></a>
</p>
指定した主なパラメータは次のとおりです。
| パラメータ | 役割 |
|---|---|
style=flat-square | バッジの形を統一する |
logo | Simple Icons由来のロゴを表示する |
logoColor=white | ロゴを白で表示する |
| URL末尾の色 | バッジ右側の背景色を指定する |
利用できる書式は、Shields.io Static Badgesで確認できます。
バッジ間に青い下線が出た
最初は、a要素とimg要素を次のように改行して書いていました。
<a href="https://example.com/">
<img src="badge.svg" alt="Example" />
</a>
GitHub上で表示を確認すると、バッジとバッジの間に意図しない青い下線が出ました。a要素内で画像の後ろに入った改行や空白も、リンクの一部として装飾されていたためです。
そこで、リンクと画像を同じ行にまとめました。
<a href="https://example.com/"><img src="badge.svg" alt="Example" /></a>
HTML属性内でクエリパラメータをつなぐ&は、&と記述しました。Markdownのソースだけを見ていたときには気づけず、GitHub上の表示確認で見つけた問題です。
About:自己紹介は3行に絞る
Aboutには、現在の専門性を表す3点だけを残しました。
## About
- Building reliable cloud infrastructure and platforms
- Improving operations through observability, SLOs, and automation
- Exploring AI-assisted operations and engineering workflows
経歴や価値観は個人サイトにあるため、ここでは繰り返していません。公開リポジトリを見る前に、私が信頼性、運用改善、AIを使ったエンジニアリングに関心を持っていると伝わる3行だけを残しました。
Skill Icons:技術名を一覧ではなく視覚で伝える
スキル表示には、Skill Iconsを使いました。iへアイコンIDをカンマ区切りで渡し、perlineで1行あたりの個数を指定できます。
<p align="center">
<a href="https://skillicons.dev">
<img src="https://skillicons.dev/icons?i=linux,redhat,raspberrypi,bash,nginx,py,django,go,java,zig,aws,gcp,azure,terraform,ansible,docker,kubernetes,grafana,git,gitlab,neovim,latex&perline=12" alt="Linux, Red Hat, Raspberry Pi, Bash, Nginx, Python, Django, Go, Java, Zig, AWS, Google Cloud, Azure, Terraform, Ansible, Docker, Kubernetes, Grafana, Git, GitLab, Neovim, and LaTeX" />
</a>
</p>
**Focus:** Observability · SLO / SLI · Platform Engineering · AIOps
アイコンは思いついた順に並べず、次のカテゴリが左から順に並ぶよう整理しました。
- OS・基盤:Linux、Red Hat、Raspberry Pi、Bash、Nginx
- 言語・フレームワーク:Python、Django、Go、Java、Zig
- クラウド・IaC・運用:AWS、Google Cloud、Azure、Terraform、Ansible、Docker、Kubernetes、Grafana
- 開発ツール:Git、GitLab、Neovim、LaTeX
Observability、SLO / SLI、Platform Engineering、AIOpsは、Skill Iconsだけでは表しにくいため、アイコンの下にテキストで残しました。用意されているアイコンに自分のプロフィールを合わせるのではなく、私が関心を持つ領域を優先しています。
GitHub Stats Extended:言語カードだけを使う
コミット数、Star数、ランクを表示するカードも試す候補にありました。ただ、活動量を競うような見せ方は、今回作りたいプロフィールとは違います。総合Statsは載せず、公開リポジトリの中身を知る手掛かりになる言語カードだけを使いました。
利用したのは、GitHub Stats ExtendedのTop Languages Cardです。
<p align="center">
<a href="https://github.com/stats-organization/github-stats-extended">
<img width="560" src="https://github-stats-extended.vercel.app/api/top-langs/?username=urchin-hat&layout=compact&langs_count=8&size_weight=0.5&count_weight=0.5&card_width=420&theme=github_dark&hide_border=true&hide_title=true" alt="Most used languages in urchin-hat's public repositories" />
</a>
</p>
主な設定は次のとおりです。
| パラメータ | 設定 | 意図 |
|---|---|---|
layout | compact | 高さを抑える |
langs_count | 8 | 上位8言語を表示する |
size_weight | 0.5 | コード量を50%加味する |
count_weight | 0.5 | 利用リポジトリ数を50%加味する |
card_width | 420 | カード生成時の幅を指定する |
theme | github_dark | ダークテーマで統一する |
hide_border | true | 枠線を隠す |
hide_title | true | README側の見出しと重複させない |
カードの生成幅を広げてみましたが、文字は期待したほど大きくなりませんでした。そのため、HTMLのwidth="560"でSVG全体を拡大しています。SVGなので、ラスター画像のような粗さは出ませんでした。
Top Languagesは「得意言語ランキング」ではない
このカードが集計するのは、本人が所有する非Forkの公開リポジトリです。仕事で書いたコード、Organizationや他ユーザーのリポジトリへの貢献、非公開リポジトリは、公開インスタンスの標準状態では反映されません。
集計はコードの意味や習熟度を評価するものではなく、生成物や大きなファイルの影響も受けます。私の業務経験を含む「得意言語ランキング」と誤解されないよう、見出しとaltは「公開リポジトリで使われている言語」と読める表現にしました。
カードの制約とオプションは、GitHub Stats ExtendedのAdvanced Documentationで確認できます。
情報の引き算:試してみて外したもの
作り始めた頃は、代表リポジトリも最新記事も、README内で見せたほうが親切だと考えていました。ところが、実際に配置するとプロフィールが縦に伸び、GitHub標準機能や外部リンクとの重複が目立ちます。一度実装したものの、最終的に2つの要素を外しました。
代表リポジトリカード:Pinned repositoriesとの重複を避ける
当初は、代表的な成果物を紹介するためにMarkdownのリストでリポジトリを記載していました。
- **[clamchowder](https://github.com/urchin-hat/clamchowder)** — プロジェクトの説明
- **[mcenv](https://github.com/urchin-hat/mcenv)** — プロジェクトの説明
この方法は軽量ですが、README側と各リポジトリ側で説明文を二重管理することになります。そこで、GitHub Stats ExtendedのRepo Cardへ置き換える方法を試しました。
<p align="center">
<a href="https://github.com/urchin-hat/clamchowder"><img width="400" src="https://github-stats-extended.vercel.app/api/pin/?username=urchin-hat&repo=clamchowder&description_lines_count=2" alt="clamchowder repository" /></a>
<a href="https://github.com/urchin-hat/mcenv"><img width="400" src="https://github-stats-extended.vercel.app/api/pin/?username=urchin-hat&repo=mcenv&description_lines_count=2" alt="mcenv repository" /></a>
</p>
Repo Cardへ任意の説明文を直接渡すオプションはありません。カードには各リポジトリのDescriptionが表示されます。そこで、リポジトリ側のDescriptionを次のように整理しました。
clamchowder:
A Bash meta-framework for standardizing operational commands, configuration, and documentation.
mcenv:
A lightweight CLI for switching between multiple MySQL .my.cnf connection profiles.
各リポジトリのDescriptionを更新すればカードにも反映されるため、READMEとの二重管理は解消できました。
しかし、プロフィール全体を表示してみると、READMEのすぐ下にはGitHub標準のPinned repositoriesがあります。Repo Cardを置くと、同じ代表リポジトリが2回続き、画面も縦に伸びました。
Descriptionの二重管理は解消できても、表示の重複は残ります。スクロール量を抑えるほうを優先し、代表リポジトリはPinned repositoriesに任せ、READMEからRepo Cardを外しました。
ブログ最新記事の自動更新:情報密度を見直して停止する
プロフィールを作り始めた段階では、技術メモへの入口として、Blog Post Workflowで最新記事3件をREADMEへ自動反映する仕組みも作りました。
HugoのRSSフィードを確認する
個人サイトはHugoで構築しています。最初にhttps://urchin-hat.org/memo/index.xmlを指定しましたが、フィードは取得できても記事が入っていませんでした。確認し直すと、投稿一覧のRSSは次のURLでした。
https://urchin-hat.org/memo/posts/index.xml
HTTP 200が返るだけでは自動更新に使えるか判断できません。item要素に記事タイトル、URL、公開日時が入っているところまで確認しました。
READMEの更新マーカー
Blog Post Workflowは、README内のコメントマーカーに挟まれた部分を書き換えます。
## Latest Posts
<!-- BLOG-POST-LIST:START -->
<!-- BLOG-POST-LIST:END -->
Workflowの定義
設定したWorkflowは次のような内容です。
name: Update latest blog posts
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
contents: write
jobs:
update-readme:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Update README with latest posts
uses: gautamkrishnar/blog-post-workflow@v1
with:
feed_list: "https://urchin-hat.org/memo/posts/index.xml"
max_post_count: 3
title_max_length: 100
commit_message: "docs: update latest blog posts"
0 0 * * *はUTCの0時、日本時間では毎日9時です。手動実行用にworkflow_dispatchも用意しました。
情報量を見直して一時停止する
Workflowで最新3件がREADMEへ入るところまでは確認できました。ただ、プロフィール全体を見ると、ヘッダーにはWebsiteとTech Memoへのバッジがあり、その下にはPinned repositoriesもあります。Latest Postsまで置くと、同じ動線を別の形で繰り返しているように見えました。
そこで、READMEからLatest Postsセクションを削除し、Workflowは次の名前へ変更して無効化しました。
.github/workflows/blog-post-workflow.yml.disabled
.github/workflows/配下でも、.ymlまたは.yamlではないファイルはWorkflowとして読み込まれません。削除はせず、試した設定を.disabledファイルとして残しました。再開するときは、拡張子とREADMEのマーカーを戻せます。
mainブランチを保護する
プロフィールREADMEも公開物です。mainの履歴を誤って書き換えたり、ブランチを削除したりしないようBranch protection ruleを設定しました。
現在の設定は次のとおりです。
| 設定 | 値 |
|---|---|
| 管理者にも保護を適用 | 有効 |
| Force push | 禁止 |
| Branch deletion | 禁止 |
| PRの未解決会話 | 解決を必須にする |
| PR必須 | 無効 |
| Status checks必須 | 無効 |
GitHubでは、保護ブランチのForce pushと削除は既定で禁止されます。詳しい挙動は、About protected branchesを参照してください。
PR必須を有効にしなかった理由
保護設定を行った時点では、Blog Post WorkflowがGITHUB_TOKENを使ってREADMEへ直接コミットする構成でした。PR必須をそのまま有効にすると、Workflowのpushが拒否される可能性があります。
また、このリポジトリはOrganizationではなく個人所有です。Classic branch protectionでは、PR必須ルールを特定のbotだけバイパスさせる構成に制約があります。そのため、自動更新を維持しながら、Force pushと削除だけを確実に防ぐ構成にしました。
現在はBlog Post Workflowを止めています。今後すべての変更をPR経由に統一するなら、PR必須を改めて有効にできます。自動更新を止めたことで当時の判断理由もなくなったため、ブランチ保護も見直す必要があります。
ブランチを分けて小さく反映する
READMEは一度に仕上げず、役割ごとにブランチを分けて変更しました。
feature/profile-readme
feature/reorder-skill-icons
feature/add-profile-badges
feature/add-language-stats
feature/add-profile-gif
feature/replace-linkedin-with-facebook
feature/add-featured-project-cards
変更のたびにGitHub上の画面を見て、アイコンの追加、カードの拡大、テーマ変更、リンク差し替えを繰り返しました。プロフィールREADMEは見た目を自動テストしにくいため、変更を小さく分けたほうが、どの差分で表示が変わったのかを追いやすくなりました。
検証したこと
READMEにはMarkdownとHTMLだけでなく、外部SVG、GIF、GitHub Actionsも含まれます。ソースの構文だけではGitHub上の表示や外部サービスの応答までは分からないため、次の内容を確認しました。
ローカルでの確認
# MarkdownやYAMLに余分な空白がないか確認
git diff --check
# WorkflowのYAML構文を確認
ruby -e "require 'yaml'; YAML.load_file('.github/workflows/blog-post-workflow.yml')"
# GIFの形式とサイズを確認
file urchin-hat.gif
identify urchin-hat.gif
外部サービスの確認
- Skill IconsのURLが画像を返すか
- Shields.ioの各バッジが生成されるか
- GitHub Stats ExtendedのLanguage CardとRepo CardがSVGを返すか
- RSSに最新記事の
itemが含まれているか - Website、Tech Memo、各リポジトリへのリンクが到達可能か
curl -fsSL --max-time 30 '<CARD_URL>' -o /tmp/card.svg
file /tmp/card.svg
HTTPステータスが成功しても、サービスによってはエラーメッセージを含むSVGが返ります。そのため、ファイルを取得できたことだけでなく、リポジトリ名や言語名など、期待する文字列が含まれていることも確認しました。
外部カードを使うときに考えておきたいこと
今回のREADMEでは、Skill Icons、Shields.io、GitHub Stats Extendedから画像を取得しています。少ない記述で表示を整えられましたが、GitHubだけでは表示が完結しません。採用するときに、私は次の点も確認しました。
外部サービスが落ちると画像も表示されない
README本体がGitHubに残っていても、画像生成サービスが停止するとカードは表示されません。外部サービスが落ちてもプロフィールの要点が残るよう、肩書きとAboutは通常のテキストで書きました。
キャッシュですぐに変わらない場合がある
リポジトリのDescriptionを変更しても、Repo Cardへすぐ反映されない場合があります。設定ミスと決めつけず、元のGitHub情報が更新されているか、カード側のキャッシュが残っているかを分けて確認する必要があります。
数字や割合を過大評価しない
言語カードに出る割合は、あくまで公開リポジトリの構成です。私の業務経験や得意分野を完全に表す数字ではないため、集計範囲が伝わる見出しとaltにしました。
情報を足すより、役割を分ける
技術アイコン、言語カード、リポジトリカード、最新記事、Pinned repositoriesは、それぞれ単体では便利です。しかし、すべてを並べた画面は、同じ情報への入口が多く、私には冗長に見えました。
今回は次のように役割を分けました。
| 要素 | 役割 |
|---|---|
| 個人サイト | 詳細な経歴、価値観、記事、趣味 |
| Profile README | 肩書き、関心領域、技術、成果物への入口 |
| Skill Icons | 扱っている技術の一覧 |
| Languages Card | 公開リポジトリの言語構成 |
| Pinned repositories | GitHub標準の成果物一覧 |
この整理を基準に、Latest PostsとRepo Cardを外しました。代表成果物はGitHub標準のPinned repositoriesに任せたため、READMEの縦幅も短くなりました。
完成したREADMEの構成
最終的な構成は次のようになりました。
Hi, I'm urchin_hat
SRE / Infrastructure Engineer
キャラクターGIF
Website / Tech Memo / Facebook / X
About
Skills
Focus
Languages
このREADMEの直下には、GitHub標準のPinned repositoriesが並びます。個人サイトの簡略版という最初の方針を保ちながら、GitHubで先に見てほしい情報を上から並べた形です。
まとめ
GitHub Profile READMEを実際に整えてみると、バッジや統計カードを追加することより、どの情報をGitHubへ置き、どの情報を個人サイトや標準機能へ任せるかが判断の中心になりました。
今回の作業で残った判断は、次の8点です。
- ユーザー名と同名のPublicリポジトリでProfile READMEを表示する
- 詳細なプロフィールは個人サイトへ任せ、GitHubは要約版にする
- Shields.ioはリンク、Skill Iconsは技術一覧と役割を分ける
- GitHub Stats Extendedは集計範囲を理解して使う
- 成果物一覧や最新記事は、標準のPinned repositoriesや外部リンクへ任せて引き算する
- HTMLの空白や
&など、Markdown内HTML特有の表示も確認する - GitHub Actionsとブランチ保護はセットで設計する
- 情報が増えすぎたら、表示を止める判断もする
最初から完成形を決めることはできませんでした。小さく変更してGitHub上で確認すると、青い下線のような表示上の問題だけでなく、Repo CardやLatest Postsの重複にも気づけます。これから活動内容が変わったときも、機能を足すことから始めず、肩書き、Focus、Pinned repositoriesの役割を見直しながら更新していきます。
参考資料
GitHub公式ドキュメント
- Managing your profile README — GitHub Docs:Profile READMEの表示要件と基本仕様
- About protected branches — GitHub Docs:ブランチ保護ルールの詳細と設定項目
使用したサービス・ツール
- Shields.io Static Badges:ナビゲーション用リンクバッジの作成
- Skill Icons:技術スタックアイコンの表示
- GitHub Stats Extended(詳細ドキュメント):言語カードやRepo Cardの生成オプション
- Blog Post Workflow:RSSからREADMEへ最新記事を自動反映するGitHub Actions
