ファイルストレージ
RailwayのS3互換ストレージバケットを使ったファイルのアップロードと配信。
アプリがユーザーアップロードファイル(画像、PDF、エクスポート)を保存する必要があるときは、RailwayのS3互換ストレージバケットを使います。ファイルはデフォルトでプライベートで、短時間有効な署名付きURLでユーザーに配信します。
リファレンス実装
クライアントを自作しないでください。このファイルをそのままアプリにコピーします:
apps/staff-portal/app/_lib/storage.ts → apps/<your-app>/app/_lib/storage.ts
2つの関数をエクスポートします: createStorageClientFromEnv()(環境変数を読み、設定済みのS3クライアントを返す)とgetPresignedUrl(storage, key, expiresIn)(時間制限付きのダウンロードURLを生成)。
依存関係を追加
アプリのpackage.jsonに:
{
"dependencies": {
"@aws-sdk/client-s3": "^3.709.0",
"@aws-sdk/s3-request-presigner": "^3.709.0"
}
}モノレポルートから pnpm install。
Railwayストレージバケットの作成
プラットフォームアプリは Railway環境ごとに1つのバケット を共有します(ステージングと本番は別インスタンス・別認証情報)。
1. Railwayでバケットを作成
2回行います — Railwayで staging を選択した状態で1回、production を選択した状態で1回。
- クライアントのRailwayプロジェクトを開く(例:
shimomoto-vibe-coding-platform)。 - 環境ドロップダウンで staging(または production)に切り替える。
- プロジェクトキャンバスで Create → Bucket。
- リージョンを選択(後から変更不可)。表示名を設定(例:
shimomoto-storage)。 - バケットのデプロイ完了を待つ。
- バケットを開き Credentials タブから以下を控える:
| Railway Credentials | プラットフォーム環境変数 | 備考 |
|---|---|---|
ENDPOINT | BUCKET_ENDPOINT | 例: https://storage.railway.app — 末尾スラッシュなし |
ACCESS_KEY_ID | BUCKET_ACCESS_KEY_ID | S3アクセスキー |
SECRET_ACCESS_KEY | BUCKET_SECRET_ACCESS_KEY | S3シークレットキー |
BUCKET | BUCKET_NAME | S3 API用バケット名(ハッシュ付き)。RAILWAY_BUCKET_NAME ではない |
REGION | BUCKET_REGION | 通常 auto |
Railwayドキュメント: Storage Buckets。
2. GitHub Environmentsに値を登録
クライアントリポジトリ(algoritmi-tech/<client>-vibe-coding-platform)で Settings → Environments を開く。
staging と production の両方に登録 — 値はそのRailway環境で作成したバケットの Credentials から:
| GitHubキー | 推奨タイプ | 取得元 |
|---|---|---|
BUCKET_ENDPOINT | Variable | Railway ENDPOINT |
BUCKET_ACCESS_KEY_ID | Secret | Railway ACCESS_KEY_ID |
BUCKET_SECRET_ACCESS_KEY | Secret | Railway SECRET_ACCESS_KEY |
BUCKET_NAME | Variable | Railway BUCKET |
BUCKET_REGION | Variable | Railway REGION |
非機密(ENDPOINT、BUCKET、REGION)は Variables、キー類は Secrets に保存。
3. ストレージを使うアプリを再デプロイ
CIはデプロイ時にGitHub Environmentの vars/secrets を読み、deploy-reusable.yml 経由で Railway サービスに BUCKET_* を設定します。
| アプリ | ワークフロー | 転送するバケット設定 |
|---|---|---|
| Database Accessor | deploy-app-database-accessor-staging/production.yml | BUCKET_* 5つすべて |
| App Portal | deploy-app-app-portal-staging/production.yml | BUCKET_SECRET_ACCESS_KEY(他は Variable から) |
| Vibe Coding Guide | deploy-app-vibe-coding-guide-staging/production.yml | BUCKET_SECRET_ACCESS_KEY(他は Variable から) |
GitHub更新後、該当デプロイワークフローを実行(または main に push)。Railway → サービス → Variables に BUCKET_ENDPOINT 等が表示されることを確認。
バケット必須のアプリ: database-accessor(ストレージブラウザ)、および storage.ts / アップロードAPIを追加したアプリ。
環境変数(ランタイム)
以下4つがすべて必要です。環境ごとにRailwayでGitHub secret経由で設定します — 環境変数を参照。
| 変数 | 目的 |
|---|---|
BUCKET_ENDPOINT | S3互換APIのエンドポイント |
BUCKET_ACCESS_KEY_ID | 認証情報 |
BUCKET_SECRET_ACCESS_KEY | 認証情報 |
BUCKET_NAME | 読み書き対象のバケット |
オプション:
BUCKET_REGION— 未設定時はauto(Railwayバケットは通常auto)。
必須変数が欠けていると、createStorageClientFromEnv()がMissing storage environment variablesをスローします。
バケット設定のトラブルシュート
| 症状 | 対処 |
|---|---|
| ストレージ画面で環境変数不足 | 正しいGitHub Environmentに BUCKET_* を設定し再デプロイ |
SignatureDoesNotMatch | BUCKET_ENDPOINT の末尾スラッシュを削除 |
| ステージングのみ動作 | 本番用バケット + GitHub production 用キーが別途必要 |
Railwayで BUCKET_SECRET_ACCESS_KEY だけ空 | アプリのデプロイワークフロー secrets: で転送する |
クライアントの使い方
import {
createStorageClientFromEnv,
getPresignedUrl,
} from '@/app/_lib/storage';
const storage = createStorageClientFromEnv();
// 7日間有効な署名付きURLを生成
const url = await getPresignedUrl(
storage,
'my-app/photos/avatar.png',
3600 * 24 * 7
);
// URLをブラウザに返す — ブラウザは直接ファイルを取得できる。バケット構成
すべてのキーにアプリ名のプレフィックスを付けてファイルをグループ化します:
my-app/photos/...
checkin-board/attendances/...
staff-portal/documents/...
こうすることで、1つのバケットが複数アプリを綺麗に扱えます。
つまづきポイント(一度は読んでおく価値あり)
これらはリファレンスのstorage.tsに組み込み済みですが、「修正」してしまわないよう覚えておきましょう:
BUCKET_ENDPOINTの末尾スラッシュを削除 — 末尾スラッシュがあるとSignatureDoesNotMatchになります。forcePathStyle: trueがRailwayのS3互換APIには必須です。- チェックサム計算は
'WHEN_REQUIRED'に設定する必要があります — AWS SDK v3 ≥ 3.600はデフォルトでチェックサムヘッダを追加し、RailwayのTigrisは403を返します。
Claudeに聞く
クイズ
バケットに保存されたファイルをユーザーにダウンロードさせる正しい方法は?