Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## Summary

<!-- 変更内容を短く書いてください。 -->

## Checks

- [ ] `task ci`

## macOS VZ test

GitHub-hosted macOS runner では VZ の実テストを安定して実行できません。必要な場合は、物理 Apple Silicon Mac 上で手動確認してください。

- [ ] Not run
- [ ] Run on physical Apple Silicon macOS

Command:

```sh
task ci:macos
```

Result / notes:

<!-- 実行した場合は結果を書いてください。未実施の場合は理由を書いてください。 -->
9 changes: 0 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,3 @@ jobs:
with:
enable-cache: "true"
- run: devbox run -- task ci

ci-macos:
runs-on: macos-15-intel
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: jetify-com/devbox-install-action@8c6a66ed6273138b1915457069de78cb52fe3bd7 # v0.15.0
with:
enable-cache: "true"
- run: devbox run -- task ci:macos
118 changes: 114 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

spind is a fast local development environment for people who build web applications that use the Kubernetes API, and for people who build Kubernetes Operators.

It supports kind-based Kubernetes environments and makes clean environments fast to create, reset, and start.
It supports kind-based and k3d-based Kubernetes environments and makes clean environments fast to create, reset, and start.

## Why spind?

Expand All @@ -29,13 +29,13 @@ On macOS, spind starts a small Linux VM with Virtualization.framework. On Linux,

The Linux VM is not a special Kubernetes system. It is a small Docker machine. You can use your normal Docker client, kind, kubectl, and helm.

spind sets up the runtime VM once, then saves that ready state as a snapshot. After that, spind restores from the snapshot. This lets you start an environment where the kind cluster and Helm charts are already prepared.
spind sets up the runtime VM once, then saves that ready state as a snapshot. After that, spind restores from the snapshot. This lets you start an environment where the Kubernetes cluster and Helm charts are already prepared.

## Use cases

- Web applications that use the Kubernetes API
- Kubernetes Operator development
- Local Kubernetes development with kind
- Local Kubernetes development with kind or k3d
- Clean environments for end-to-end tests and integration tests
- Faster Kubernetes environment setup in CI/CD

Expand Down Expand Up @@ -86,7 +86,7 @@ Put `spind.yaml` in your project root.
```yaml
name: sample
image: docker
kind: true
k8s: kind
setup:
- "kind create cluster"
- "kubectl --context kind-kind wait node --all --for=condition=Ready --timeout=180s"
Expand All @@ -100,6 +100,116 @@ spind up

On the first run, spind creates a VM, runs the commands in `setup`, and saves the ready state as a snapshot. Later runs restore from that snapshot.

## Guide for kind users

Use `k8s: kind` when your project uses kind.

```yaml
name: sample
image: docker
k8s: kind
setup:
- "kind create cluster"
- "kubectl --context kind-kind wait node --all --for=condition=Ready --timeout=180s"
```

Run `spind up`.

```sh
spind up
```

After the environment starts, spind prints shell exports for the restored VM.

```sh
export DOCKER_HOST='unix:///.../.spind/vms/sample/docker.sock'
export KUBECONFIG='/.../.spind/vms/sample/kubeconfig'
```

Use those values in the current shell, then use your usual tools.

```sh
kubectl get nodes
docker ps
```

spind does not update your global kubeconfig by default. The kubeconfig path printed by `spind up` is the VM-specific kubeconfig.

## Guide for k3d users

Use `k8s: k3d` when your project uses k3d.

```yaml
name: k3d-sample
image: docker
k8s: k3d
setup:
- "k3d cluster create k3d-sample --registry-create k3d-sample-registry --kubeconfig-update-default=false"
- 'k3d kubeconfig get k3d-sample > "$SPIND_KUBECONFIG"'
- "kubectl --context k3d-k3d-sample wait node --all --for=condition=Ready --timeout=180s"
```

`SPIND_KUBECONFIG` is a path that spind sets for setup commands. Write the k3d kubeconfig there so spind can save it in the snapshot and rewrite it after restore.

When the k3d cluster has a local registry, spind detects it and prints `REGISTRY`.

```sh
export DOCKER_HOST='unix:///.../.spind/vms/k3d-sample/docker.sock'
export KUBECONFIG='/.../.spind/vms/k3d-sample/kubeconfig'
export REGISTRY='localhost:61702'
```

Use `REGISTRY` with the `DOCKER_HOST` value printed by spind.

```sh
docker build -t "$REGISTRY/my-app:dev" .
docker push "$REGISTRY/my-app:dev"
```

For k3d, `k3d registry list` may show a port that is different from the `REGISTRY` value printed by spind. In a spind environment, use the `REGISTRY` value printed by `spind up` or `spind vm start`.

See `examples/k3d/spind.yaml` for a k3d sample with cert-manager.

## Guide for Tilt users

Tilt works with the same `DOCKER_HOST` and `KUBECONFIG` values that spind prints.

Start the spind environment first.

```sh
spind up
```

Then export the values printed by spind in your shell.

```sh
export DOCKER_HOST='unix:///.../.spind/vms/tilt-sample/docker.sock'
export KUBECONFIG='/.../.spind/vms/tilt-sample/kubeconfig'
export REGISTRY='localhost:61702'
```

Tilt can auto-detect a k3d local registry through the `kube-public/local-registry-hosting` ConfigMap. For that flow, do not set `default_registry()` in the Tiltfile.

A minimal Tiltfile looks like this.

```python
allow_k8s_contexts("spind-tilt-sample")

docker_build("tilt-sample", ".")

k8s_yaml("k8s.yaml")

k8s_resource("tilt-sample", port_forwards="8080:80")
```

Run Tilt after the spind environment is ready.

```sh
tilt up
```

See `examples/tilt/` for a small Tilt sample.

## Development

Use these commands when working on this repository.
Expand Down
4 changes: 3 additions & 1 deletion devbox.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"terraform": "latest",
"kind": "latest",
"kubectl": "latest",
"kubernetes-helm": "latest"
"kubernetes-helm": "latest",
"k3d": "latest",
"tilt": "latest"
},
"shell": {
"init_hook": ["export PATH=\"$PWD/bin:$PATH\"", "echo 'spind devbox ready'"]
Expand Down
96 changes: 96 additions & 0 deletions devbox.lock
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,54 @@
}
}
},
"k3d@latest": {
"last_modified": "2026-06-11T01:27:03Z",
"resolved": "github:NixOS/nixpkgs/b503dde361500433ca25a32e8f4d218bf58fb659#k3d",
"source": "devbox-search",
"version": "5.9.0",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/g91b1mz18bq5q567naa54kvka1ci6466-k3d-5.9.0",
"default": true
}
],
"store_path": "/nix/store/g91b1mz18bq5q567naa54kvka1ci6466-k3d-5.9.0"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/4ly383iik2mca7561g5y4sinvmdmkbxl-k3d-5.9.0",
"default": true
}
],
"store_path": "/nix/store/4ly383iik2mca7561g5y4sinvmdmkbxl-k3d-5.9.0"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/lyx64rqz0hc1b6rcyxmsw1cz9qh1lbcf-k3d-5.9.0",
"default": true
}
],
"store_path": "/nix/store/lyx64rqz0hc1b6rcyxmsw1cz9qh1lbcf-k3d-5.9.0"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/v69k5siw9w41lnbz34dk9nwkn5f3dbi2-k3d-5.9.0",
"default": true
}
],
"store_path": "/nix/store/v69k5siw9w41lnbz34dk9nwkn5f3dbi2-k3d-5.9.0"
}
}
},
"kind@latest": {
"last_modified": "2026-05-21T08:15:18Z",
"resolved": "github:NixOS/nixpkgs/4a29d733e8a7d5b824c3d8c958a946a9867b3eb2#kind",
Expand Down Expand Up @@ -752,6 +800,54 @@
"store_path": "/nix/store/f8yvzw63qd1mc44d8bx6hq7smr7zvxnr-terraform-1.15.4"
}
}
},
"tilt@latest": {
"last_modified": "2026-05-21T08:15:18Z",
"resolved": "github:NixOS/nixpkgs/4a29d733e8a7d5b824c3d8c958a946a9867b3eb2#tilt",
"source": "devbox-search",
"version": "0.37.3",
"systems": {
"aarch64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/srcj3zqmc8k83pmhcvzimmwyanx9bv05-tilt-0.37.3",
"default": true
}
],
"store_path": "/nix/store/srcj3zqmc8k83pmhcvzimmwyanx9bv05-tilt-0.37.3"
},
"aarch64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/l5lcv1pma6a6sv9a11m1af875hx1l3vd-tilt-0.37.3",
"default": true
}
],
"store_path": "/nix/store/l5lcv1pma6a6sv9a11m1af875hx1l3vd-tilt-0.37.3"
},
"x86_64-darwin": {
"outputs": [
{
"name": "out",
"path": "/nix/store/57n2dz3pq26g0410f4zpc6c62d0gy2j9-tilt-0.37.3",
"default": true
}
],
"store_path": "/nix/store/57n2dz3pq26g0410f4zpc6c62d0gy2j9-tilt-0.37.3"
},
"x86_64-linux": {
"outputs": [
{
"name": "out",
"path": "/nix/store/a0mypx8rw6gpd0fc9jjbws7vqkj5113r-tilt-0.37.3",
"default": true
}
],
"store_path": "/nix/store/a0mypx8rw6gpd0fc9jjbws7vqkj5113r-tilt-0.37.3"
}
}
}
}
}
Loading