tea-e7n.dev

miseのdaemons機能を利用して、APIサーバーのportを自動的に割り当てる

mise v2026.9.12で、daemons機能がかなり実用的になったので、その一端を確認していこうかなと思います。

試すこと

mise daemonsを利用することで、port割り当てを自動的に行なってからAPIサーバーを起動するという内容を試します。

また、git worktreeと組み合わせることにより、別々のportで複数のAPIサーバーを起動するという実験も行います。

APIサーバーの準備

今回は、次のようなコードでAPIサーバーを動かすことにします。

package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
)

const defaultPort = 18080

func main() {
	port := defaultPort
	if v := os.Getenv("API_PORT"); v != "" {
		p, err := strconv.Atoi(v)
		if err != nil || p < 1 || p > 65535 {
			log.Fatalf("invalid API_PORT %q", v)
		}
		port = p
	}

	pid := os.Getpid()

	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok\n"))
	})
	mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		fmt.Fprintf(w, "Hello World! (pid=%d, port=%d)\n", pid, port)
	})

	addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(port))
	log.Printf("api listening on http://%s (pid=%d)", addr, pid)
	if err := http.ListenAndServe(addr, mux); err != nil {
		log.Fatal(err)
	}
}

mise.tomlの準備

mise.tomlは次のような内容で準備しています。

[settings]
experimental = true

[tools]
go = "1.25"
pitchfork = "2.25"

[daemons_settings]
namespace = "mise-worktree-aware-daemons"

[tasks.check]
description = "api daemon を起動して /health と / を確認する"
daemons = "api"
run = """
curl -fsS "http://127.0.0.1:${API_PORT}/"
"""

[daemons.api]
init = "go build -o bin/api ."
run = "exec ./bin/api"
port = { auto = true, base = 18080 }
ready_cmd = "curl -fsS http://127.0.0.1:$API_PORT/health"

詳細については、後で動作を確認しながら説明するので、ここでは省略します。

APIサーバーを起動する

では、まずは試しにAPIサーバーを起動してみましょう。 どうやって起動するんだってツッコミがあるかもしれませんが、一旦深くは考えずに、[tasks.check]を実行します。

mise run check
- [mise-worktree-aware-daemons/api] api listening on http://127.0.0.1:18080 (pid=38928)
 [mise-worktree-aware-daemons/api] started on port 18080
[check] $ curl -fsS "http://127.0.0.1:${API_PORT}/"
Hello World! (pid=38928, port=18080)

ということで、なんか動いてはいそうです。まずはこれについて簡単に説明します。

ログを見ると、checkタスクを実行した直後にdaemonが起動しているのがわかります。 これは、[tasks.check] daemons = "api"を定義しているためです。 この定義があると、taskのrunの内容を実行する前に、依存daemonを起動します。(今回は1つのみ指定してますが、list形式で複数指定もできます)

daemon自体の定義は、[daemons.api]にあります。 着目すべきは、port = { auto = true, base = 18080 }の記述です。 この定義により起動されるdaemonは、{daemons name}_PORTの形式の環境変数として、自動的に選択されたport番号を持ちます。 今回は[daemons.api]なので、API_PORT=xxxの形式で環境変数を持つことになります。 その上で、runコマンドによりAPIサーバーを起動します。

daemonの準備が完了したかどうかは、ready_cmdにより制御できます。 今回だと、サーバーが起動してhealth checkが通ったら準備完了となるように定義しています。

そして、daemonが起動して初めて、[tasks.check]のrunが実行されます。 サーバーはすでに起動済みなので、リクエストを投げたら当然結果が返ってくるという話です。

ちなみに、ここで起動したdaemonについては、次のコマンドで起動済みであることを確認できます。

mise daemons ls
Daemon                           Status   Source
mise-worktree-aware-daemons/api  running  xxx

また、daemonの停止もコマンドで可能となっています。

mise daemons stop api

mise daemons ls
Daemon                           Status   Source
mise-worktree-aware-daemons/api  stopped  xxx

git worktreeと組み合わせて利用する

次に、worktreeを別に切って、そこでもAPIサーバーを起動してみます。 前述の通りですが、先ほどの章で起動したdaemonは起動済みであるとします。 その場合に他のdaemonを起動したらどうなるでしょうという実験です。

まずは適当にworktreeを切って、移動します。

git worktree add ../sandbox-wt -b check-mise-worktree-daemons
cd ../sandbox-wt

そして先ほどと同様、サーバーを起動します。

mise run check

- [mise-worktree-aware-daemons-1b56ec03d80d87fc/api] api listening on http://127.0.0.1:18372 (pid=40699)

 [mise-worktree-aware-daemons-1b56ec03d80d87fc/api] started on port 18372
[check] $ curl -fsS "http://127.0.0.1:${API_PORT}/"
Hello World! (pid=40699, port=18372)

先ほどはportが18080だったのが、今回は18372となりました。 port = { auto = true, base = 18080 }と設定しているので、daemonはworktreeごとに18080を基準として別々のポートを割り当てます。その結果、今回は18372が割り当てられました。 (自動的に空きportを解決して割り当てるというわけではないので注意が必要)

ちなみに、今回起動したdaemonは前章で起動したdaemonとは別物であることが確認できます。

mise daemons ls
Daemon                                         Status   Source
mise-worktree-aware-daemons-1b56ec03d80d87...  running  xxx

まとめ

miseに導入されたdaemons機能を利用することにより、プロセスをdaemonとして起動してtaskを実行できることを確認しました。 プロセス管理などが原則不要で、mise.tomlに定義するだけであとはいい感じにやってくれるのは嬉しいポイントだと思いました。

また、git worktreeを利用して複数daemonを起動したときの挙動についても確認しました。 環境ごとに別々のdaemonが起動して、port割り当ても自動的に行なってくれるという挙動は、AI Agentなどを利用した並列開発のスタイルと相性がいいと感じました。

まだexperimentalな機能ということで、GAが楽しみです。