@deno/sandbox で使える機能に、Persistent Volumes が増えていました。 こちら、SandBox内での永続ボリューム/ストレージを提供する機能です。
こちらを使い、git 使ったメモツール/サイトを作ってみました。
参考
Persistent Volumes とは Persistent Volumes は、Deno SandBox で立ち上げられるマイクロインスタンスにリージョンブロックストレージをアタッチする機能。 これを使うと都度立ち上がるマイクロインスタンスに、セットアップされたストレージを提供できる。
公式のドキュメントでは、以下のように紹介されています。
Persistent volumes let you attach regional block storage to a sandbox so data survives process restarts and new connections. They are ideal for package caches, build artifacts, SQLite databases, or any workflow that needs a small amount of durable storage without promoting code to a full Deno Deploy app.
パッケージキャッシュ、ビルドアーティファクト、SQLiteデータベースなどを利用例として挙げられています。
2025年12月30年日時点では、プライベートベータ版使うためには、サポートへの連絡が必要です。
とてもミニマムな利用例としては、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import { Client , Sandbox } from "@deno/sandbox" ;const client = new Client ();const volume = await client.volumes .create ({ slug : "storage" , region : "ord" , capacity : "300MiB" , }); console .log ("=== ファイル書き込み処理サンドボックス ===" );{ await using sandbox = await Sandbox .create ({ volumes : { "/data/storage" : volume!.id , }, region : "ord" , }); await sandbox.writeTextFile ("./volatile-memo.md" , "volatile storage test memo" ); console .log (`> ls .\n${await sandbox.sh`ls .` .text()} ` ); await sandbox.writeTextFile ("/data/storage/persistent-memo.md" , "Persistent storage test memo" ); await sandbox.sh `sync` .sudo (); console .log (`> ls /data/storage\n${await sandbox.sh`ls /data/storage` .text()} ` ); } console .log ("=== ファイル読み込み処理サンドボックス ===" );{ await using sandbox = await Sandbox .create ({ volumes : { "/data/storage" : volume!.id , }, region : "ord" , }); console .log (`> ls .\n${await sandbox.sh`ls .` .text()} ` ); console .log (`> ls /data/storage\n${await sandbox.sh`ls /data/storage` .text()} ` ); } await client.volumes .delete (volume!.id );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 > deno run -EN --env sample.ts deno run -EN --env .\test2-sudo-volume-minimum-sample.ts === ファイル書き込み処理サンドボックス === > ls . volatile-memo.md > ls /data/storage persistent-memo.md === ファイル読み込み処理サンドボックス === > ls . > ls /data/storage persistent-memo.md
このように、. 置いたファイルは2回目に起動したサンドボックスでは消えていますが、/data/storage 配下のファイルは残っています。 公式ドキュメントには無いですが、sync コマンドを実行しておかない場合、ファイルが保存されない場合があるようです。
git を Persistent Volumes において使う。 特定パッケージをPersistent Volumesに置いて使う例として、git を置いてみます。
github - Octo8080X/deno-sandbox-memo に置いたものがあります。 全体を見たい場合、こちらをご覧ください。
sandboxGit.ts(抜粋) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 import { Client , Sandbox , SandboxOptions , Volume } from "@deno/sandbox" ;const GIT_STORAGE_VOLUME_NAME = "git-storage-volume" ;const SERVER_APP_STORAGE_VOLUME_NAME = "git-server-app-storage-volume" ;const DATA_STORAGE_VOLUME_NAME = `${Deno.env.get("APP_ENV" )} -git-data-storage-volume` async function getStorageVolume (slug: string ) { const client = new Client (); let volume : Volume | null = null ; const volumes = await client.volumes .list (); if (volumes.items .filter ((v ) => v.slug === slug).length > 0 ) { volume = await client.volumes .get (slug)!; } else { volume = await client.volumes .create ({ slug, region : "ord" , capacity : "300MiB" , }); } return volume; } const createSandbox = async (options?: SandboxOptions ) => { const sandbox = await Sandbox .create ({ memoryMb : 4096 , region : "ord" , ...options, }); return sandbox; }; const withSandbox = async <T>( fn : (sandbox: Sandbox ) => Promise <T>, options?: SandboxOptions , ): Promise <T> => { await using sandbox = await createSandbox (options); return await fn (sandbox); }; export const SERVER_APP_ENTRYPOINT = "/data/server_app/server.ts" ;export const SERVER_APP_SANDBOX_OPTIONS : SandboxOptions = { volumes : { "/data/server_app" : SERVER_APP_STORAGE_VOLUME_NAME , "/data/git" : GIT_STORAGE_VOLUME_NAME , "/data/storage" : DATA_STORAGE_VOLUME_NAME , }, env : { GIT_CONFIG_GLOBAL : "/data/git/.gitconfig" , }, region : "ord" , memoryMb : 4096 , timeout : "10m" , }; const buildGitSandboxOptions = (volumes : Record <string , string >): SandboxOptions => { return { volumes, env : { GIT_CONFIG_GLOBAL : "/data/git/.gitconfig" , }, region : "ord" , memoryMb : 4096 , }; }; export async function initSandBoxStorage ( ) { const gitStorageVolume = await getStorageVolume (GIT_STORAGE_VOLUME_NAME ); const storageVolume = await getStorageVolume (DATA_STORAGE_VOLUME_NAME ); const gitSandboxOptions = buildGitSandboxOptions ({ "/data/git" : gitStorageVolume!.id , "/data/storage" : storageVolume!.id , }); await withSandbox (async (sandbox) => { console .log ("initialize volume on sandbox" ); console .log ("apt update" ); await sandbox.sh `apt-get update > /dev/null 2>&1` .sudo (); console .log ("install git" ); await sandbox.sh `apt-get install -y git` .sudo (); console .log (await sandbox.sh `git --version` .text ()); console .log ("stage git binaries into /data/git volume" ); await sandbox.sh `mkdir -p /data/git/app /data/git/lib` .sudo (); await sandbox.sh `sh -c 'cd /tmp && apt-get download git git-man'` .sudo (); await sandbox.sh `dpkg -x /tmp/git_* /data/git/app` .sudo (); await sandbox.sh `dpkg -x /tmp/git-man_* /data/git/app` .sudo (); await sandbox .sh `sh -c 'set -e; ldd /data/git/app/usr/bin/git | awk "/=>/ {print $3}" | grep -E "^/" | while read -r path; do echo "lib: $path"; cp -n "$path" /data/git/lib/; done'` .sudo (); await sandbox.sh `sh -c 'cat <<"EOF" > /data/git/git #!/bin/sh export LD_LIBRARY_PATH=/data/git/lib:\${LD_LIBRARY_PATH} export GIT_EXEC_PATH=/data/git/app/usr/libexec/git-core export PATH=/data/git/app/usr/bin:/data/git/app/usr/libexec/git-core:\${PATH} exec /data/git/app/usr/bin/git "$@" EOF'` .sudo (); await sandbox.sh `chmod +x /data/git/git` .sudo (); await sandbox.sh `/data/git/git --version` .sudo (); console .log (await sandbox.sh `ls -la /data/git | head` .text ()); await sandbox.sh `sync` .sudo (); console .log ("configure git identity (volume git)" ); await sandbox.sh `/data/git/git config --global user.name "sandbox"` ; await sandbox .sh `/data/git/git config --global user.email "sandbox@example.com"` ; console .log ("init repo into storage volume using volume git" ); await sandbox .sh `cd /data/storage && /data/git/git init && /data/git/git branch -m main && /data/git/git add -A && /data/git/git commit --allow-empty -m "initial commit"` ; console .log ("list git log from volume" ); await sandbox .sh `cd /data/storage && /data/git/git log` ; await sandbox.sh `sync` .sudo (); }, gitSandboxOptions); const serverAppStorageVolume = await getStorageVolume (SERVER_APP_STORAGE_VOLUME_NAME ); const serverAppSandboxOptions : SandboxOptions = { volumes : { "/data/server_app" : serverAppStorageVolume!.id , }, region : "ord" , }; await withSandbox (async (sandbox) => { await sandbox.fs .writeTextFile ("/data/server_app/server.ts" , Deno .readTextFileSync ("./sandboxServerApp/server.ts" )); await sandbox.fs .writeTextFile ("/data/server_app/deno.json" , Deno .readTextFileSync ("./sandboxServerApp/deno.json" )); await sandbox.sh `cd /data/server_app && deno install` ; await sandbox.sh `ls -la /data/server_app` .sudo (); await sandbox.sh `sync` .sudo (); }, serverAppSandboxOptions); } if (import .meta .main ) { await initSandBoxStorage (); }
apt-get install により取得された tmp/git を/data/git 配下に展開。 依存ファイル群も、/data/git/lib 以下にコピーしています。 そして、/data/git/git に依存ライブラリのパスなど記述し、本体gitを実行するシェルスクリプトを作成しています。
1 2 3 4 5 #!/bin/sh export LD_LIBRARY_PATH=/data/git/lib:\${LD_LIBRARY_PATH} export GIT_EXEC_PATH=/data/git/app/usr/libexec/git-coreexport PATH=/data/git/app/usr/bin:/data/git/app/usr/libexec/git-core:\${PATH} exec /data/git/app/usr/bin/git "$@ "
最初これを扱うとき、起動の都度sandboxを起動することをしていました。 しかし、同一のPersistent Volumesをマウントすることは仕様的にできないようです。 そのため、gitを操作するsandboxをある程度の時間起動をキープし、サーバアプリを介して操作するようにしました。 Webアプリのバックエンドとして扱うならば、レスポンスの観点でこの対応をすることが望ましいと考えます。
Deno sandobox Persistent Volumes の上で、gitを使う例を紹介しました。 これを使ったアプリ も稼働状態で公開しています。
では。