How to configure local workloads
Complete installation and initialize the runtime. Use unused machine names. The examples below are independent.
(require ' [ol.smolvm :as smol])
Copy staged work back to the host
Create /tmp/smol-work on the host before running this example.
A staged mount gives the guest a local working copy. Call sync to copy changes
back without stopping the machine:
(with-open [machine (smol/create
{:name "staged-work"
:mounts [{:source "/tmp/smol-work"
:target "/workspace"
:staged? true }]})]
(smol/file-write machine "/workspace/result.txt" "finished" )
(smol/sync machine))
Read /tmp/smol-work/result.txt on the host to check the result.
Graceful stop and deletion also copy staged changes back.
Staged mounts cannot be read-only or use s3:// sources.
Do not use them with pause or checkpoint capture.
Synchronization can change host files. Use a dedicated work directory,
not an irreplaceable source directory. sync is not a general disk flush.
|
Publish a guest service
Request a free host TCP port, then read the assigned mapping:
(with-open [machine (smol/create {:name "service-example"
:ports [{:host 0 :guest 8080 }]})]
{:host-port (smol/host-port machine 8080 )
:mappings (smol/published-ports machine)})
Start your service inside the same scope before using that port.
The example only allocates forwarding. A mapping does not prove that a guest
service is listening. guest-ports lists forwarding targets, not listeners.
To replace mappings, call ports-set with the complete desired vector.
TCP mappings can change while running when the native ports control socket
supports the operation. For UDP changes, stop the machine first:
(with-open [machine (smol/create {:name "udp-example" })]
(smol/stop machine)
(smol/ports-set machine [{:host 0 :guest 5353 :protocol :udp }])
(smol/start machine)
(smol/published-ports machine))
Pass [] to remove all mappings. Read published-ports after an update
for effective host ports. start waits for guest command execution,
not application readiness.
Add or remove one mapping
Use publish instead of replacing the full vector yourself. This example
connects to a machine whose native control socket supports live TCP changes:
(with-open [machine (smol/connect "existing-machine" )]
(let [mapping (smol/publish machine {:guest 8080 })]
(try
(println "Published:" mapping)
(finally
(smol/unpublish machine mapping)))))
Omitting :host selects a free host port. The result includes :host,
:guest and :protocol. The example removes that mapping before closing
its handle. unpublish matches all supplied selectors and defaults to TCP.
For UDP, stop the machine before either operation.
Serialize port changes across all handles and processes for a machine.
These helpers read and replace the mappings, so concurrent updates can be lost.
Free-port selection releases its socket before publication, allowing another
process to take the port. A readback error can follow a successful update.
Inspect published-ports before deciding whether to retry.
|
Check a service and read its HTTP response
First start your service and publish its guest port. The following example
assumes an existing machine named http-service, an HTTP service on guest
port 8080, and a published mapping for that port:
(with-open [machine (smol/connect "http-service" )]
(println "Probe:" (smol/ready? machine))
(smol/wait-until-ready machine {:timeout-ms 10000 :interval-ms 250 })
(let [urls (smol/endpoint machine 8080 "/health" )
response (smol/fetch machine 8080 "/health" {:timeout-ms 5000 })]
(with-open [^java.io.InputStream body (:body response)]
{:endpoint urls
:status (:status response)
:text (slurp body :encoding "UTF-8" )})))
Check both :status and the service’s response content. HTTP error statuses
are returned, not thrown. Always close the response body, including for errors.
The request timeout limits receipt of headers, not subsequent body reads.
Without :timeout-ms, fetch has no request timeout.
The readiness probe is a TCP heuristic, not an application health check or UDP check. For each published guest port it connects within 250 ms, then tries to read one byte within 100 ms. A byte or read timeout counts as ready. EOF, reset or connection failure does not. This can consume a greeting byte and produce service logs. A running machine with no guest ports passes. Native state or mapping errors propagate.
Waits are explicit. Create, connect, start, resume and branch do not call
wait-until-ready automatically. Its defaults are 120 seconds and a one-second
interval. A wait timeout or thread interruption does not stop or delete the
machine. The deadline does not interrupt an active call or probe.
This helper does not wait for a process result.
endpoint returns :http-url, :ws-url and empty :headers for the current
loopback mapping. It does not send a request, probe or publish a port.
Use URI-valid paths and percent escapes. Missing mappings throw CONFIG_ERROR.
fetch follows HTTP/HTTPS redirects, including HTTPS to HTTP. Redirects can
leave the local machine. Redirect limits and header forwarding follow Java’s
HTTP client, not Go’s exact policy.
Build a request for another HTTP method
Use http-request to get request data for babashka.http-client:
(with-open [machine (smol/connect "http-service" )]
(smol/http-request machine 8080 "/jobs"
{:method :post
:headers {"Content-Type" "application/json" }
:body "{\"command\":\"refresh\"}" }))
This returns a map with :method, :uri, :headers and :body, without
sending HTTP. It reads the current native port mapping. Omit :method for GET.
Pass the map to babashka.http-client/request with the response and transport
options you need. Keep any supplied input-stream body open through request
completion. Keep the target machine running until the request finishes.
For input and result shapes, see Data specs.
Restrict outbound access
Stop an existing machine before replacing its policy. Closing a connected handle leaves that machine intact:
(with-open [machine (smol/connect "existing-machine" )]
(smol/stop machine)
(smol/egress-policy-set machine {:network? false
:allowed-cidrs ["192.0.2.0/24" ]})
(smol/start machine))
Replace the example CIDR with the network your workload needs.
Use {:network? false} with no allow lists to deny outbound access, or
:network? true for unrestricted access. The engine validates policy rules.
Saved-memory machines cannot switch network backends.
An egress interceptor is a separate service, not supplied by this library.
Run image-based work
Pull an image into machine storage, inspect the cache, then run a short command:
(with-open [machine (smol/create {:name "image-work" :network? true })]
(smol/image-pull machine "alpine:3.21" )
(smol/image-list machine)
(smol/run machine "alpine:3.21" ["echo" "hello" ]))
Image pulling needs registry access. run also pulls if the image is not cached.
Check :exit-code and :stdout in its result. It buffers all output and rejects
:stdin, :user and :cancel. For a particular guest user, create an
image-backed machine and use exec instead.
Start work without waiting for its exit
Use exec-background when you only need a guest PID. Connect to an existing
machine so closing the handle does not end the work:
(with-open [machine (smol/connect "existing-machine" )]
(smol/exec-background machine ["sleep" "60" ]))
The machine continues running after this scope. Stop or delete it explicitly
when its work is complete. If you use a create handle instead, keep that
handle open until the work finishes.
The guest PID is not the host VM PID returned by pid.
There is no background result, output capture or cancellation handle.
For exit status and cancellation, use process or exec.
See Local API reference for complete configuration and option tables.