# How to configure local workloads Complete [installation](installation.adoc) and initialize the runtime. Use unused machine names. The examples below are independent. ```clojure (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: ```clojure (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. **⚠️ WARNING**\ 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: ```clojure (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: ```clojure (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: ```clojure (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. **⚠️ WARNING**\ 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: ```clojure (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`: ```clojure (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](data-specs.adoc). ## Restrict outbound access Stop an existing machine before replacing its policy. Closing a connected handle leaves that machine intact: ```clojure (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: ```clojure (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: ```clojure (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](local-commands.adoc). See [Local API reference](api/ol-smolvm.adoc) for complete configuration and option tables.