Skip to main content

Flags and Modes

What is the difference between --persistent and --persist?

These are two separate flags that control different things:
FlagPurposeScope
--persistentKeep a container alive between runs for stateful workflowsExecution mode
--persistKeep the container running after execution for debuggingPost-execution cleanup
--persistent sets the execution mode to persistent. The container is reused across multiple execute() calls, preserving filesystem state, environment variables, and installed packages between runs. This is for workflows where you need to build up state incrementally (e.g., write a file in one execution and read it in the next). --persist prevents the container from being cleaned up after execution finishes. This is a debugging tool — it lets you docker exec into the container after your code has run to inspect the filesystem, check logs, or diagnose issues. Without --persist, ephemeral containers are returned to the pool and wiped, and persistent containers are stopped when stop() is called. You can use both together: --persistent --persist gives you a stateful session whose container remains running even after stop().

What is the difference between --persist and cleanup.autoPrune?

These operate at completely different levels:
SettingLevelControls
--persistEngine (per-execution)Whether a container is cleaned up after a single execution
cleanup.autoPruneConfig (server-level)Whether isol8 serve periodically prunes idle sessions
--persist (or persist: true in the library) is an engine-level option. It affects the container used for a specific execution. When true, the container is left running after the code finishes. Default: false. cleanup.autoPrune is a config-level option under the cleanup section. It only affects the HTTP server (isol8 serve). When true, the server periodically scans for idle sessions older than cleanup.maxContainerAgeMs (default: 1 hour) and removes them. Default: true. Setting persist: true does not prevent server-side auto-pruning. If you need a container to survive indefinitely on the server, you should also set cleanup.autoPrune: false in your config or increase maxContainerAgeMs.

What does --debug do?

--debug enables verbose internal logging from the isol8 engine. When set, internal operations are printed to the console with a [DEBUG] prefix:
  • Container pool operations (acquire, release, replenish)
  • Container lifecycle events (create, start, stop, remove)
  • Persist/cleanup decisions
  • Image resolution
--debug does not affect the output of your executed code. It only controls isol8’s own internal logs. Without --debug, these logs are completely suppressed.
# See what isol8 is doing internally
isol8 run -e "print('hello')" --runtime python --debug

How do --persist and --debug interact?

They are independent flags:
--persist--debugBehavior
falsefalseDefault. Containers are cleaned up silently.
falsetrueContainers are cleaned up, but you see internal logs about the cleanup.
truefalseContainers are left running, but you get no internal logs.
truetrueContainers are left running, and you see internal logs about the persist decision.

What is the difference between ephemeral and persistent mode?

AspectEphemeralPersistent
Container lifecycleFresh container per execution (from warm pool)Same container reused across executions
State preservationNone — /sandbox is wiped between runsFull — files and env persist
Runtime switchingEach execution can use a different runtimeLocked to one runtime per container
Performance~55-95ms (warm pool)~200-400ms first run, then ~50ms
Use caseStateless one-off executionsMulti-step workflows, REPL-like sessions
Ephemeral mode (default) draws from a warm container pool. After execution, the container’s /sandbox directory is wiped and the container is returned to the pool for reuse by the next execution. This is fast and secure — each execution starts with a clean slate. Persistent mode creates a dedicated container that lives across multiple execute() calls. Files written in one execution are visible in the next. The container is only destroyed when stop() is called (or when the server prunes it if cleanup.autoPrune is enabled).
Persistent containers are locked to a single runtime. Attempting to run Python after Node.js on the same persistent container throws an error. Create a separate DockerIsol8 instance for each runtime you need.

Networking

When should I use none vs filtered vs host network mode?

ModeSecurityUse case
noneHighestDefault. Code that doesn’t need network access.
filteredMediumCode that needs specific API endpoints (e.g., OpenAI, GitHub).
hostLowestCode that needs unrestricted network access. Use with trusted code only.
none (default) disables all network access. The container has no network interface. DNS, HTTP, TCP — nothing works. This is the most secure option. filtered gives the container bridge networking with an HTTP/HTTPS proxy that filters requests by hostname. Use --allow and --deny regex patterns to control which hosts are reachable. Non-HTTP protocols (raw TCP, UDP, WebSocket) are blocked. host gives the container full access to the host’s network. Only use this when you trust the code or genuinely need unrestricted connectivity.

Why does --net in the CLI ignore the config file default?

The --net flag has a Commander-level default of "none", which means it always has a value when the CLI runs. This effectively overrides the defaults.network setting from your config file. To use a different network mode, pass --net explicitly:
isol8 run script.py --net filtered --allow "^api\.example\.com$"
This is a deliberate safety choice — network access should be an explicit opt-in, not something silently inherited from a config file.

Runtimes

Why do .ts files run with Bun instead of Deno?

Both Bun and Deno can execute TypeScript, but .ts files default to Bun because it is registered first in the runtime adapter registry. To run TypeScript with Deno, either:
  • Use the .mts file extension, or
  • Pass --runtime deno explicitly
# Runs with Bun (default for .ts)
isol8 run script.ts

# Runs with Deno
isol8 run script.mts
isol8 run script.ts --runtime deno

Why can’t I use -e (inline code) with Deno?

Deno does not support inline code execution. The Deno adapter requires a file path — it throws an error if no file is provided:
Error: Deno adapter requires a file path — inline code is not supported.
To run Deno code, provide it as a file (via the files field in ExecutionRequest or by passing a file argument to the CLI).

Why does my persistent container reject a different runtime?

Persistent containers are created with a specific runtime’s Docker image (e.g., isol8:python). Each image only contains its own language toolchain. A Python container doesn’t have Node.js installed, so attempting to switch runtimes fails:
Error: Cannot switch runtime from "python" to "node". Each persistent
container supports a single runtime. Create a new Isol8 instance for
a different runtime.
Create a separate DockerIsol8 instance (or a separate persistent session on the server) for each runtime you need.

Resource Limits

Why do packages fail with “Operation not permitted”?

Packages that include native extensions (like numpy, pandas, or scipy) compile and install shared library files (.so). These files need to be executable, which means they must be installed in a directory that allows execution. isol8 installs packages to /sandbox (which has the exec flag), not /tmp (which has noexec). The default /sandbox size is 512MB, which is sufficient for most packages. If installation fails with “No space left on device” or “Operation not permitted”, increase the sandbox size:
isol8 run -e "import numpy" --runtime python --install numpy --sandbox-size 1g

What is the difference between --sandbox-size and --tmp-size?

MountDefaultExecutionPurpose
/sandbox512MBAllowedWorking directory, package installations, user files
/tmp256MBBlocked (noexec)Temporary files, package manager caches
/sandbox is where your code runs, packages are installed, and output files are written. It allows execution of shared libraries. /tmp is for temporary files that programs create during execution. The noexec flag on /tmp is a security measure — it prevents untrusted code from downloading and executing arbitrary binaries in the temp directory.

Why was my output truncated?

Output (stdout + stderr) is capped at maxOutputSize (default: 1MB / 1,048,576 bytes). When output exceeds this limit, it is truncated and result.truncated is set to true. This prevents memory exhaustion from programs that produce excessive output (e.g., infinite loops printing to stdout). Increase the limit with --max-output:
isol8 run generate_report.py --max-output 10485760  # 10MB

Secrets

How does secret masking work?

When you pass --secret KEY=VALUE, isol8:
  1. Injects KEY as an environment variable inside the container
  2. Scans all stdout and stderr for any occurrence of VALUE
  3. Replaces every occurrence with ***
isol8 run -e "import os; print(os.environ['TOKEN'])" \
  --runtime python --secret TOKEN=sk-abc123
# stdout: ***
Masking happens after code execution, on the collected output. The code itself has full access to the secret value — only the output seen by the caller is sanitized. This prevents accidental leakage in logs, AI agent responses, or user-facing output.

Can secrets leak through file outputs?

Secret masking only applies to stdout and stderr. If your code writes a secret value to a file and you retrieve that file via getFile(), the secret will not be masked in the file contents. Only the streamed/collected text output is sanitized. To avoid this, do not write secrets to files that you intend to retrieve, or implement your own masking on retrieved file contents.