Spin
An Embeddable WebAssembly container runtime.
Why?
Containers are lightweight, portable, and have minimal CPU overhead. However, they require a runtime (e.g., Docker), lack robust isolation, and are not universally portable across different OSes or CPU architectures.
Running containers in a secure and restricted environment is crucial when the code cannot be trusted, such as when it's generated by third parties or AI.
It's also beneficial to have a fallback in case the host operating system lacks a runtime.
Additionally, this enables the embedding of commands and services written in various languages into your code.
Architecture
spin
consists of a wasm runtime, an emulator for amd64/riscv64 compiled to wasm, and a minimal OS image that launches your container inside the emulator.
Usage
Registry
A container image registry.
import "github.com/taubyte/tau/pkg/spin/registry"
r, err := New(context.TODO(), "path/to/storage")
Then, pull:
err = r.Pull(context.TODO(), imageName, nil)
To get image path:
imagePath, err := r.Path(imageName)
Runtime
Execute containers sharing the same runtime or base module.
Using Registry
import (
"github.com/taubyte/tau/pkg/spin/registry"
"github.com/taubyte/tau/pkg/spin/runtime"
)
r, _ := New(context.TODO(), "path/to/storage")
s, _ := runtime.New(context.TODO(), runtime.Runtime[runtime.RISCV64](r))
r.Pull(context.TODO(), "riscv64/hello-world:latest", nil)
c, _ := s.New(runtime.Image("riscv64/hello-world:latest"))
c.Run()
Output:
Hello from Docker!
Not using Registry
import "github.com/taubyte/tau/pkg/spin/runtime"
s, _ := runtime.New(context.TODO(), runtime.Runtime[runtime.RISCV64](r))
c, _ := s.New(runtime.ImageFile("path/to/file"))
c.Run()
A good example is TestPullContainer
.
Executing Commands
If you'd like to execute a command, use the Command
options. Example:
c, err := s.New(Command("ls","/import"))
Export Environment variables
Exporting Environment Variables is done using the Env
options. Example:
c, err := s.New(Env("SPIN","is awesome"),Command("sh","-c","\"echo \$SPIN\""))
Mount a folder
To mount a folder to the container, use the Mount
options. Example:
c, err := s.New(Mount("/path/on/host","/path/in/container"),Command("ls","/path/in/container"))
Note: WASI does not export permissions.
Networking
To enable networking just use the Networking()
option when creating the container. Options like Forward
can be used for port forwarding.
Example:
c, err := s.New(Networking(Forward("18080", "8080")))
See TestNetServiceContainer
and TestNetContainer
for more details.
Limitations
Currently, WebAssembly is single-threaded, so only one CPU can be emulated. Generally, expect execution speeds to be about 10 times slower than native code on a single core.
Pull speed
Right now, image compression may take a few minutes as it uses mksquashfs
, which is CPU intensive, running on spin
. Later we'll create two images, one using mkisofs
, which will be used temporarily, and another one with mksquashfs
in the background.
Memory
WebAssembly modules cannot address more than 4GB and wizer is having a hard time optimizing the module when the memory exceeds 1GB. Thus, the runtime is presently limited to 1GB. Plans include using swap files that are in-memory on the host side to increase container memory in the future.