statik を使って Go バイナリに静的ファイルを埋め込む

statik を使って Go バイナリに静的ファイルを埋め込む

Go でウェブアプリケーションを開発すると、バイナリと HTML ページ、CSS スタイル、JavaScript バンドル、画像、フォントなどの静的アセットを管理するのが煩雑になります。すべてを含む単一の実行ファイルを配布することで、デプロイが簡素化され、ファイルパスの混乱や本番環境でのリソース不足エラーを回避できます。Go エコシステムではこの目的のために便利なヘルパーツール statik が提供されています。

statik は小さなコマンドラインツールで、ディレクトリをスキャンして http.FileSystem にコンテンツを登録する Go ソースファイルを生成します。

このガイドでは、ツールのインストール、埋め込みファイルの生成、コード内での利用方法、そしていくつかのベストプラクティストのヒントを解説します。


statik を使う理由

  • 1 ファイルでデプロイ – 外部ファイルを持たず、1 つのバイナリを出荷します。Docker イメージ、サーバーレス関数、CLI にアセットを埋め込む場合に便利です。
  • 型安全なアクセス – 生成されたパッケージは型付き fs.FileSystem を提供し、コンパイル時にエラーが早期に検出されます。
  • 決定論的な出力 – デフォルトで statik はファイルの修正時刻を保持します。CI 用にオプションフラグで無効にできます。

1. statik CLI をインストールする

# Go 1.22+ では `go install` と @latest が利用できます
go install github.com/rakyll/statik@latest

古い Go バージョンでは手動でモジュールを取得します。

go get github.com/rakyll/statik

バイナリは環境によって $GOPATH/bin あるいは $HOME/go/bin に配置されます。


2. 静的アセットフォルダを準備する

public(好きな名前でも可)というフォルダを作成し、そこにアセットを置きます。

└─ public/
   ├─ index.html
   ├─ styles.css
   ├─ script.js
   └─ images/
       └─ logo.png

好きなディレクトリ名を使って構いません。statik に渡すパスを覚えておいてください。


3. Go ソースを生成する

フォルダに statik を実行します。一般的なオプションは次のとおりです。

  • -src : ソースディレクトリへのパス。
  • -srcpath : 生成されるコードが属するパッケージ名。
# 典型的な使用例
statik -src=./public

これで statik フォルダと statik.go ファイルが作成され、public 内のすべてが登録されます。

拡張子でフィルタリング

単一のバイナリに特定のファイルタイプだけを含めたい場合は -include フラグを使用します(サイズを抑えるため)。

statik -src=./public -include=*.html,*.css,*.js

除外したい場合は次のようにします。

statik -src=./public -exclude=*.png,*.jpg

変更時刻を無視する

CI パイプラインでは Git の checkout 時に開発時と異なる mtime が付与され、決定論的テストが壊れることがあります。-m フラグで抑制できます。

statik -m -src=./public

4. コードで埋め込みファイルシステムを利用する

生成されたパッケージをインポートし、fs.FileSystem を初期化します。

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/rakyll/statik/fs"

    // 生成されたパッケージをインポートします。パッケージ名は -srcpath が生成したものになります。
    _ "./statik"
)

func main() {
    // 新しい statik ファイルシステムを作成
    statikFS, err := fs.New()
    if err != nil {
        log.Fatalf("statik: %v", err)
    }

    // 必要なら単一ファイルを読み込む
    r, err := statikFS.Open("/index.html")
    if err != nil {
        log.Fatalf("open: %v", err)
    }
    defer r.Close()
    content, _ := io.ReadAll(r)
    fmt.Println("Loaded \"index.html\" content:", string(content)[:100])

    // HTTP でコンテンツを配信
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(statikFS)))
    log.Println("Serving on http://localhost:8080/static/")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

プログラムを実行します。

go run main.go

http://localhost:8080/static/index.html を開いて、バイナリから配信される静的ページを確認してください。


5. ヒントとトリック

  • アセットは小さく保つ – statik は合計サイズが数メガバイト以内だと最適に動作します。
  • -md5 フラグを使う(将来機能、まだリリースされていません)で MD5 チェックサムを保持し整合性を確認します。
  • 大きなバイナリの再ビルドを避ける – アセットが変更された時だけ statik を再実行します。Makefile ターゲットや git フックで自動化すると便利です。
  • 決定論的ビルドを確認する – 単体テストで生成された FS からファイルを開き、内容やタイムスタンプをアサートできます。

6. statik を使える場面

  • ローカルテンプレートを必要とする CLI ツール
  • サーバーレス Go 関数 – 依存関係とアセットを直接埋め込む。
  • デスクトップ Go アプリ – アイコン、ヘルプファイル、設定テンプレートを含める。

このツールはアクティブにメンテナンスされており、新リリースや改善点は GitHub リポジトリ で確認できます。


結論

Go バイナリに静的アセットをバンドルすることで、デプロイがスムーズになり、外部依存が減ります。statik は Go の http.FileSystem とシームレスに統合できるエレガントでゼロ設定のソリューションです。上記手順に従うことで、数分で静的ファイルを生成・埋め込み・配信でき、プロダクション環境を軽量で簡潔に保てます。

この記事を共有