ストレージとデータ
TODO(内部向け・リリース前に対応して削除)
Section titled “TODO(内部向け・リリース前に対応して削除)”Keelson のストレージの考え方
Section titled “Keelson のストレージの考え方”Keelson のアプリには、2種類のファイルシステム領域があります。
| 領域 | 再デプロイ時 | 用途 |
|---|---|---|
| アプリディレクトリ(ソースコード等) | 置き換わる | アプリのコード、依存パッケージ |
/data | 残る | データベース、アップロードファイル、永続データ |
永続化したいものは /data に置いてください。 それ以外の場所に書き込んだデータは、再デプロイ時に失われます。
/data はアプリごとに分離されており、他のアプリからはアクセスできません。ワークスペース単位で容量が管理され、データは東京リージョンに保存されます。
/data に保存できるもの
Section titled “/data に保存できるもの”/data には、アプリが必要とするあらゆるファイルを保存できます。
- SQLite データベース — 顧客管理、案件管理、ログなどのアプリデータ
- アップロードファイル — ユーザーがフォームから送信した画像、PDF、CSV など
- 生成済みレポート — 集計結果やエクスポートファイル
- キャッシュデータ — 外部 API のレスポンスや計算結果の再利用
- 設定ファイル — アプリが実行時に生成する設定やステートファイル
社内ツールでよくある使い方:
/data/main.db # アプリのメインデータベース/data/uploads/ # ユーザーがアップロードしたファイル/data/reports/ # 日次・週次で生成したレポート/data に保存しない方がよいもの
Section titled “/data に保存しない方がよいもの”以下は /data 以外の方法を検討してください。
- 巨大なバイナリファイル — 動画ファイルや大容量のデータセットはストレージ容量を圧迫します。外部のオブジェクトストレージ(S3 互換サービスなど)を検討してください
- 一時ファイル — 処理中だけ必要なファイルは
/tmpを使い、処理後に削除してください - 再生成可能なキャッシュ — npm の
node_modulesやビルド成果物は/dataに置く必要はありません - 大規模ログの長期保存 — アプリログは Keelson のログ機能で閲覧できます。独自の大量ログを
/dataに蓄積するとストレージを消費します - 高い可搬性が求められるデータ — 他のシステムとの頻繁なデータ連携が必要な場合は、外部データベースが適しています
SQLite を使う
Section titled “SQLite を使う”SQLite は Keelson でもっとも推奨されるデータベースです。外部の DB サーバーを用意する必要がなく、/data にファイルを置くだけで使えます。
なぜ SQLite が向いているか
Section titled “なぜ SQLite が向いているか”- セットアップ不要 — DB サーバーの構築・接続設定が不要
- AI 生成との相性 — ChatGPT や Claude が生成するアプリの多くは SQLite を使う構成になる
- 永続化が自動 —
/dataに置けば再デプロイ後もデータが残る - バックアップ対象 — 日次スナップショットの対象に含まれる
keelson.yaml での設定
Section titled “keelson.yaml での設定”databases: - name: main type: sqlite path: /data/main.db複数のデータベースを使うこともできます。
databases: - name: customers type: sqlite path: /data/customers.db - name: logs type: sqlite path: /data/logs.db- 単一インスタンス前提 — Keelson のアプリは1つのインスタンスで動作します。複数インスタンスからの同時書き込みを考慮する必要はありません
- 書き込みが非常に多い場合 — 秒間数百回以上の書き込みが発生するようなケースでは、外部データベース(PostgreSQL など)を検討してください
- データ量が大きい場合 — 数 GB を超える SQLite ファイルはパフォーマンスに影響する可能性があります。データ量が大きくなる見込みがある場合は、早めに外部 DB への移行を計画してください
ファイルアップロードを扱う
Section titled “ファイルアップロードを扱う”ユーザーがアップロードしたファイルは /data 配下に保存します。
保存先の設計
Section titled “保存先の設計”/data/uploads/├── 2026-03/│ ├── a1b2c3d4-report.pdf│ └── e5f6g7h8-photo.jpg└── 2026-04/ └── i9j0k1l2-invoice.csvファイル名の衝突回避
Section titled “ファイル名の衝突回避”ユーザーがアップロードしたファイル名をそのまま使うと、同名ファイルが上書きされます。UUID やタイムスタンプをプレフィックスに付けてください。
import uuid, os
def save_upload(file, upload_dir="/data/uploads"): filename = f"{uuid.uuid4().hex}_{file.filename}" path = os.path.join(upload_dir, filename) with open(path, "wb") as f: f.write(file.read()) return pathストレージ容量はプランによって異なります(Starter: 10 GB 〜 Business: 100 GB)。この容量には /data のデータ、スナップショット、ログの合計が含まれます。
不要になったファイルは定期的に削除する仕組みを入れておくと安心です。定期実行ジョブ(cron)で古いファイルを自動削除するのも有効です。
セキュリティ上の注意
Section titled “セキュリティ上の注意”- アップロードされたファイルの拡張子やサイズを検証してください
- ユーザーが指定したファイル名をそのままパスに使わないでください(ディレクトリトラバーサルの防止)
- Keelson の認証プロキシにより、アプリ自体へのアクセスは保護されていますが、アプリ内でのファイルアクセス制御はアプリ側の責務です
バックアップと復元
Section titled “バックアップと復元”日次スナップショット
Section titled “日次スナップショット”Keelson は /data のスナップショットを毎日自動で取得します。保持日数はプランによって異なります。
| プラン | スナップショット保持 |
|---|---|
| Starter | 1日分 |
| Plus | 3日分 |
| Team | 7日分 |
| Business | 14日分 |
手動スナップショットは、SQLite 管理画面から任意のタイミングで取得できます(全プラン)。
バックアップ対象
Section titled “バックアップ対象”| 対象 | バックアップされるか |
|---|---|
/data 配下のファイル(SQLite、アップロードファイルなど) | される |
| アプリのソースコード | されない(デプロイ時に毎回送信される) |
| 環境変数 | されない(ダッシュボードで管理) |
アプリのコードは Git などのバージョン管理で保持し、データは Keelson のスナップショットで保護されます。
スナップショットからの復元が必要な場合は、ダッシュボードから操作できます。復元はアプリ単位で行われ、他のアプリのデータには影響しません。
外部データベースや外部ストレージを使うべき場合
Section titled “外部データベースや外部ストレージを使うべき場合”/data + SQLite は多くの社内アプリに十分ですが、以下のケースでは外部サービスの利用を検討してください。
| ケース | 理由 | 選択肢 |
|---|---|---|
| データ量が数 GB を超える | SQLite のパフォーマンスが低下する可能性 | PostgreSQL(アドオンで専用 DB を利用可能) |
| 同時書き込みが非常に多い | SQLite はライター1つの制約がある | PostgreSQL、MySQL |
| BI や基幹系と直接連携する | データを外部システムから参照する必要がある | 共有の PostgreSQL、データウェアハウス |
| 厳密な運用要件がある | ポイントインタイムリカバリや複雑なレプリケーションが必要 | マネージド PostgreSQL |
| 大容量ファイルを大量に扱う | ストレージ容量を超える可能性 | S3 互換オブジェクトストレージ |
よくある設計パターン
Section titled “よくある設計パターン”小規模業務アプリ
Section titled “小規模業務アプリ”顧客管理や在庫管理など。SQLite にデータを保存し、アップロードファイルも /data に配置。
databases: - name: main type: sqlite path: /data/main.db/data/├── main.db└── uploads/CSV の取込・加工を行うアプリ。入力と出力を分離して管理。
/data/├── inbox/ # 取込対象の CSV├── outbox/ # 加工済みの出力└── processed/ # 処理済みの入力(アーカイブ)AI アプリ
Section titled “AI アプリ”社内 RAG や AI チャットのバックエンド。ナレッジベースのデータや添付資料を /data に配置。
/data/├── knowledge.db # ナレッジのメタデータ├── documents/ # 参照ドキュメント└── embeddings.cache # 前処理済みの埋め込みキャッシュ定期実行ジョブで集計し、結果を /data に保存。Web UI から閲覧。
/data/├── stats.db└── reports/ ├── 2026-03-01.json ├── 2026-03-02.json └── ...よくある失敗
Section titled “よくある失敗”/tmp やアプリディレクトリに保存して消える
Section titled “/tmp やアプリディレクトリに保存して消える”/tmp やアプリのソースコードと同じディレクトリに書き込んだデータは、再デプロイ時に失われます。永続化したいデータは必ず /data に置いてください。
# NG — 再デプロイで消えるdb_path = "./data.db"db_path = "/tmp/data.db"
# OK — 再デプロイ後も残るdb_path = "/data/main.db"再デプロイで初期化される場所に DB を置く
Section titled “再デプロイで初期化される場所に DB を置く”SQLite のパスを /data 以外に設定すると、デプロイのたびにデータが消えます。keelson.yaml の databases[].path が /data/ で始まっていることを確認してください。
バックアップの仕組みを誤解する
Section titled “バックアップの仕組みを誤解する”スナップショットは日次で自動取得されますが、保持日数はプランによって異なります。重要なデータを扱う場合は、プランの保持日数を確認し、必要に応じてアプリ側でもエクスポート機能を用意してください。
/data を肥大化させる
Section titled “/data を肥大化させる”不要になったファイルを削除せずに放置すると、ストレージ容量を圧迫します。ストレージ容量にはスナップショットやログも含まれるため、アプリデータだけで上限いっぱいにならないよう注意してください。
ローカルのパスをハードコードする
Section titled “ローカルのパスをハードコードする”/Users/tanaka/Desktop/data.db のようなローカルパスがコードに残っていると、Keelson 上では動作しません。環境変数または /data への相対的なパスを使ってください。
import os
# OK — 環境変数から読むdb_path = os.environ.get("DB_PATH", "/data/main.db")サンプルコード
Section titled “サンプルコード”Python: SQLite でデータを保存する
Section titled “Python: SQLite でデータを保存する”import sqlite3, os
DB_PATH = os.environ.get("DB_PATH", "/data/main.db")
def get_db(): conn = sqlite3.connect(DB_PATH) conn.execute(""" CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) return conn
# データの追加conn = get_db()conn.execute("INSERT INTO items (name) VALUES (?)", ("サンプルアイテム",))conn.commit()conn.close()keelson.yaml:
slug: my-appruntime: python-slimcommand: "pip install --user -r requirements.txt && python app.py"env: PORT: "8080" DB_PATH: "/data/main.db"
databases: - name: main type: sqlite path: /data/main.dbNode.js: SQLite でデータを保存する
Section titled “Node.js: SQLite でデータを保存する”const Database = require("better-sqlite3");const path = process.env.DB_PATH || "/data/main.db";
const db = new Database(path);db.exec(` CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )`);
// データの追加db.prepare("INSERT INTO items (name) VALUES (?)").run("サンプルアイテム");Python: ファイルアップロードを保存する
Section titled “Python: ファイルアップロードを保存する”from fastapi import FastAPI, UploadFileimport uuid, os
app = FastAPI()UPLOAD_DIR = os.environ.get("UPLOAD_DIR", "/data/uploads")os.makedirs(UPLOAD_DIR, exist_ok=True)
@app.post("/upload")async def upload(file: UploadFile): filename = f"{uuid.uuid4().hex}_{file.filename}" filepath = os.path.join(UPLOAD_DIR, filename) with open(filepath, "wb") as f: content = await file.read() f.write(content) return {"filename": filename, "path": filepath}Node.js: ファイルアップロードを保存する
Section titled “Node.js: ファイルアップロードを保存する”const express = require("express");const multer = require("multer");const crypto = require("crypto");const path = require("path");const fs = require("fs");
const app = express();const uploadDir = process.env.UPLOAD_DIR || "/data/uploads";fs.mkdirSync(uploadDir, { recursive: true });
const storage = multer.diskStorage({ destination: uploadDir, filename: (req, file, cb) => { const prefix = crypto.randomBytes(8).toString("hex"); cb(null, `${prefix}_${file.originalname}`); },});
app.post("/upload", multer({ storage }).single("file"), (req, res) => { res.json({ filename: req.file.filename, path: req.file.path });});/data が向いているケース
Section titled “/data が向いているケース”- 社内アプリの業務データ — 顧客情報、案件、在庫など、SQLite で十分な規模
- ユーザーがアップロードするファイル — PDF、CSV、画像など
- AI で生成したアプリをすぐに動かしたい — 外部 DB の設定なしでデータ永続化が使える
- 小さなアプリを複数動かしたい — アプリごとに
/dataが分離されている
/data が向いていないケース
Section titled “/data が向いていないケース”- データ量が数十 GB を超える — ストレージ容量とパフォーマンスの両面で外部 DB を検討
- 複数システムからの同時アクセスが必要 —
/dataはアプリ内からのみアクセス可能 - ポイントインタイムリカバリが必須 — スナップショットは日次のため、より細かい復元が必要ならマネージド DB を検討
- BI ツールや基幹システムとの直接連携 — 外部からクエリできるデータベースが必要
よくある質問
Section titled “よくある質問”/data の容量上限はどのくらいですか?
Section titled “/data の容量上限はどのくらいですか?”プランによって異なります(Starter: 10 GB 〜 Business: 100 GB)。この容量には /data のデータ、スナップショット、ログの合計が含まれます。追加ストレージはアドオンで購入できます。
再デプロイするとデータは消えますか?
Section titled “再デプロイするとデータは消えますか?”/data に保存したデータは消えません。アプリのソースコード領域は置き換わりますが、/data は再デプロイ後もそのまま残ります。
SQLite 以外のデータベースは使えますか?
Section titled “SQLite 以外のデータベースは使えますか?”アプリ内で完結するデータベース(SQLite など)はそのまま使えます。外部の PostgreSQL や MySQL に接続することも可能です。アドオンで専用 PostgreSQL を利用することもできます。
他のアプリから /data にアクセスできますか?
Section titled “他のアプリから /data にアクセスできますか?”できません。/data はアプリごとに完全に分離されています。アプリ間でデータを共有する必要がある場合は、API 経由でやり取りしてください。
バックアップからの復元にかかる時間はどのくらいですか?
Section titled “バックアップからの復元にかかる時間はどのくらいですか?”データ量によりますが、一般的な社内アプリの規模であれば数分で完了します。
AI への依頼例
Section titled “AI への依頼例”「データベースは SQLite を使い、
/data/main.dbに保存してください」
「アップロードされたファイルは
/data/uploads/に UUID 付きのファイル名で保存してください」
「環境変数
DB_PATHからデータベースのパスを読み取るようにしてください。デフォルトは/data/main.dbです」