27 min read

How to Install iii for a Self-Hosted Backend

Install and verify iii, scaffold a backend project, start it locally, and expose its confirmed HTTP endpoint securely with Localtonet.

An iii backend running locally and connected to a remote client through a Localtonet tunnel.
The workflow runs iii locally, verifies its HTTP endpoint, and then exposes that endpoint through Localtonet.
Developer Tools ยท iii Installation ยท Localtonet ยท 2026

Build a local iii backend, verify it carefully, then make its HTTP surface available remotely

iii is a backend runtime organized around Workers, Functions, and Triggers. This guide walks through the current documented installation command, version verification, project scaffolding, startup, local endpoint discovery, and routine troubleshooting. Because the generated project determines which HTTP endpoint is available, we will verify the actual local address instead of assuming a hostname or port. Once that endpoint works locally, we will expose it through an HTTP tunnel with Localtonet as a separate remote-access step.

๐Ÿ”’ Verify locally before creating public access ๐ŸŒ Publish a confirmed HTTP endpoint โšก Install, scaffold, run, and troubleshoot iii

Understand the iii runtime before installing it

iii is designed to provide one runtime surface for capabilities that would otherwise be assembled from separate backend systems. Its core mental model consists of three primitives: Worker, Function, and Trigger. A Worker is a process participating in the iii system. A Function is a named unit of work hosted by a Worker. A Trigger describes what causes a Function to run.

A Trigger can represent a direct function call, HTTP request, cron schedule, queue subscription, state change, stream event, or another supported event source. Workers register their Functions and Triggers with the iii engine, allowing the system to route work between participating processes. This model is useful when a backend contains services written in different languages or needs several execution patterns without treating every new capability as an unrelated integration.

โš™๏ธ Workers host capabilities A TypeScript API, Python pipeline, Rust service, or another compatible process can participate as a Worker and register what it can do.
๐Ÿงฉ Functions define work Functions have stable identifiers and receive input, perform work, and optionally return output to the caller.
โšก Triggers start execution HTTP endpoints, schedules, queues, state changes, streams, and direct calls can act as Triggers for registered Functions.
๐Ÿ” Live system visibility The iii console is intended for inspecting workers, functions, triggers, queues, traces, logs, and runtime state.

The current project workflow is concise: install the iii CLI and engine, initialize a project, enter the generated directory, and run the project with Compose. The generated project declares its Workers in worker-compose.yaml. That file is important because the services in a iii project are not inferred from the project directory name alone. The declarations determine what starts and which capabilities become available.

iii uses more than one software license

The iii engine is licensed under the Elastic License 2.0. The CLI, SDKs, console, and documentation are licensed under Apache License 2.0. Because the engine is not under an OSI-approved open-source license, review the applicable license terms before adopting iii in an environment with distribution, managed-service, or commercial policy requirements.

Prerequisites and decisions to make first

The official installation path uses a shell script downloaded with curl. You therefore need a command-line environment capable of running that command, network access to the installation host, and permission to install or place the resulting executable where your shell can find it. The supplied installation evidence does not establish a complete operating-system support matrix, package-manager alternative, hardware minimum, or every dependency used by generated Workers. We will not infer those details.

The base iii installation and the generated project are separate layers. Installing iii gives you the CLI and engine. A generated project can then include Workers that rely on additional language runtimes or external systems. Inspect the generated README and worker-compose.yaml before starting the project, especially if it contains TypeScript, Python, Rust, queues, or other components with their own runtime requirements.

Prepare the following before continuing:

  • A shell in which curl and the downloaded installer can run.
  • Permission to install the iii executable and add its location to your command search path if required.
  • A writable parent directory for the generated project.
  • A second terminal or another way to test the application while the iii process remains active.
  • Any language runtimes or services explicitly requested by the generated project.
  • A Localtonet account and client installation only if you plan to add remote access after local verification.
Review remote installation scripts according to your security policy

Piping a downloaded script directly into a shell executes it immediately. The command below is the installation method documented by iii, but organizations may require downloading, inspecting, approving, or internally mirroring installation scripts before execution. Follow your environment's software-supply-chain policy rather than bypassing it.

Keep the engine and SDKs aligned

iii documents that the engine and SDK packages can have different patch versions within the same minor release line. They should remain on the same minor version, such as all components using 0.11.x, unless a release note explicitly says otherwise. This matters when a Worker connects successfully at the transport level but fails because its SDK and engine disagree about a protocol or feature.

Record the engine version after installation and compare it with the SDK versions declared by your project. Do not automatically treat different patch numbers as a mismatch. First compare the major and minor components, then check the relevant release notes if the project requires an exception.

Install and verify iii

The current iii installation documentation defines a two-step process: run the installer, then ask the installed CLI for its version. Complete both steps before scaffolding a project. A successful download alone does not prove that the executable is available to the shell that will run the project.

1

Install the iii engine and CLI

Run the documented installer from a suitable shell. If your organization requires prior review of remote scripts, complete that review before executing the command.

2

Verify the installed command

Run the version command in the shell that you will use for development. It should print a version number. Do not proceed until this command succeeds.

curl -fsSL https://install.iii.dev/iii/main/install.sh | sh
iii --version

Save the returned version in your project notes or issue report. It is one of the first details needed when diagnosing a CLI behavior difference, SDK compatibility issue, or documentation mismatch.

If the command is not found

A successful installer message followed by iii: command not found usually means the current shell cannot locate the installed executable. Read the installer's output for any path instructions, start a fresh shell if it changed shell configuration, and inspect your command search path. The evidence available for this guide does not establish one universal installation directory, so do not assume a path or move the binary blindly.

Also confirm that installation and verification happened under the intended user account. Installing as one account and running as another can produce path or permission differences. If your environment manages software centrally, use the location approved by its administrator rather than adding an arbitrary directory to the path.

Scaffold and start a self-hosted iii project

Three-stage flow from scaffolding an iii project to starting its local HTTP service.
Scaffolding creates the project, and starting the runtime makes its HTTP service available locally.

Once iii --version succeeds, use the current project workflow from the iii README. It initializes a directory named myapp, changes into it, and starts the engine together with the project's declared Workers.

1

Initialize the project

Run iii project init myapp from the parent directory where the new project should be created. Choose a different directory name if needed, but use that same name in the next step.

2

Enter the generated directory

Change into myapp. Review the generated README and worker-compose.yaml before running unfamiliar Workers or installing additional dependencies.

3

Start the engine and project Workers

Run iii compose --up from the project root. Keep this terminal available so you can observe startup output and identify the endpoints registered by the running project.

iii project init myapp
cd myapp
iii compose --up

Do not treat creation of the directory as proof that the backend is running. The initialization command scaffolds the project. The Compose command starts the engine and Workers. A usable HTTP endpoint additionally requires an HTTP Trigger or another HTTP-facing component to be declared and started successfully.

Inspect the generated project before changing it

Begin with worker-compose.yaml and the generated README. Identify each declared Worker, its runtime requirements, and any configuration values the template expects you to supply. If an HTTP endpoint is present, determine the exact local hostname or IP address, port, and path from the generated configuration and current runtime output.

This inspection step is particularly important because iii supports many primitives beyond HTTP. A Worker can expose Functions that are called internally, subscribe to queues, respond to schedules, or process stream events without creating a browser-accessible route. A successful Worker registration therefore does not automatically imply that an HTTP URL exists.

Do not assume a development port

The current installation and project-init evidence does not specify a universal hostname, listening port, or route for the HTTP endpoint generated by iii project init. Use the generated project files, its README, and startup output to find the real endpoint. Guessing a common development port can expose the wrong service or produce misleading tests.

Avoid mixing current and versioned quickstarts

Older versioned iii documentation describes a different quickstart that uses iii create --template quickstart --directory quickstart and starts the engine with iii --config iii-config.yaml. That workflow also describes a template containing TypeScript, Python, and Rust components. It is valid evidence for that older documentation version, but it is not the same command sequence as the current README workflow used in this article.

Choose one documentation generation that matches your installed iii version. Do not initialize with one workflow, copy configuration from another version, and then assume the resulting errors are networking problems. Start by checking iii --version, the generated files, and the documentation version associated with the commands you followed.

Find and verify the actual local HTTP endpoint

The local endpoint reported by iii is tested at the same address and returns HTTP 200.
Use the endpoint reported at startup and confirm that it responds before creating a tunnel.

Remote access should be the final part of the workflow, not the first debugging tool. Before creating a tunnel, prove that the iii engine starts, the intended Worker registers, its HTTP Trigger is active, and the endpoint responds from the machine running the project.

Use the following verification sequence:

  1. Watch the terminal running iii compose --up for startup failures or dependency errors.
  2. Confirm that the intended Worker has started rather than only the engine process.
  3. Inspect the generated project configuration and output for the exact HTTP bind address, port, and route.
  4. Request that exact URL locally with a browser or an HTTP client.
  5. Check the response status and body against the behavior defined by the generated project.
  6. Repeat the request after restarting the project to confirm that the workflow is reproducible.

The endpoint must be written as a complete local target. For example, your notes should distinguish between a base service such as http://127.0.0.1:PORT and a specific Trigger route such as http://127.0.0.1:PORT/PATH. The uppercase placeholders here are intentional. Replace them only with values confirmed by your running project.

curl -i http://127.0.0.1:PORT/PATH

If the project binds to a different hostname or IP address, use that confirmed value instead. If the route requires a particular HTTP method, request body, header, or authentication credential, reproduce those requirements in the local test. A browser address bar sends a simple GET request and is not a complete test for a POST endpoint or an authenticated API.

Separate engine health from endpoint health

A running engine can accept Worker connections while a specific Worker is absent. A Worker can also register Functions without registering an HTTP Trigger. Finally, an HTTP listener can be active while the requested path returns a client or application error. These are different failure layers and should be tested separately.

Layer What to verify What failure usually means
iii CLI iii --version prints a version The installation, executable path, or permissions need attention
Engine The Compose startup output shows the engine remaining active Configuration, startup, or local dependency failure
Worker The intended Worker starts and registers its capabilities A Worker runtime, dependency, SDK, or configuration problem
HTTP Trigger The expected HTTP route is registered and listening No HTTP Trigger exists, or its route or listener is misconfigured
Application response A local request returns the expected status and body The request method, path, payload, authentication, or business logic is incorrect
Localtonet tunnel The public URL reaches the same confirmed local service The tunnel target, selected device, tunnel state, or local reachability is incorrect

Use the iii console when appropriate

iii provides a developer and operations console for inspecting workers, functions, triggers, queues, traces, logs, and real-time state. In the older documented quickstart, the console is started in a separate terminal with the following command:

iii console

That versioned quickstart documents the console at http://localhost:3113/. Treat this as a console detail from that documented workflow, not as proof that your current project's application endpoint uses port 3113. Confirm console behavior against the version you installed before relying on this address.

Optional Docker deployment workflow

The standard project commands are appropriate for learning and local development. iii also documents a Docker-oriented deployment workflow for a fresh project or an existing project. This is optional and should not be mixed into the initial setup unless you specifically want generated Docker assets.

For a fresh project, the documented option is iii project init --docker. For an existing project, run the following command from the project:

iii project generate-docker

The generator emits three files at the project root: Dockerfile, docker-compose.yml, and .env. Re-running the generator does not overwrite existing files, allowing local edits to remain in place. Review all three files before starting the stack, particularly the environment file and any network exposure configured in the Compose definition.

Start the generated stack with:

docker compose up -d

The generated docker-compose.yml documents three transport surfaces. These ports are evidence-backed for the generated Docker deployment assets. They should not be generalized to every project-init workflow without inspecting the generated configuration.

Port Documented service Remote-access consideration
49134 SDK WebSocket for Worker connections This is not interchangeable with the REST API. Do not expose it merely because it is reachable.
3111 REST API This is the HTTP-oriented surface in the generated Docker deployment, but confirm that the required route exists before tunneling it.
3112 Stream API Streaming behavior can have proxy requirements that differ from an ordinary request-response endpoint.

The generated Dockerfile builds against iiidev/iii:latest and is documented as distroless and non-root. The Compose file also includes commented-out Redis and RabbitMQ services that can be enabled when Workers require those external adapters. Do not enable infrastructure simply because it appears in a template. Enable it only when a declared Worker needs it, then configure credentials and persistence according to your deployment policy.

TLS and reverse-proxy boundaries

The iii engine does not terminate TLS. The iii deployment documentation recommends placing a reverse proxy in front of it when a conventional production deployment needs TLS and routing for the REST, stream, and WebSocket surfaces. Its example mapping sends /api/* to port 3111, /stream/* to port 3112, and /ws to port 49134.

An HTTP tunnel with Localtonet provides a public HTTPS address for the selected local HTTP target, with TLS terminated at the tunnel edge. That can be suitable when the goal is to publish one confirmed HTTP service without configuring inbound router forwarding. It does not automatically combine all three iii transport ports behind one path-routing policy. If an application needs REST, stream, and WebSocket surfaces on one origin, first configure and verify an appropriate local reverse proxy, then point the HTTP tunnel at that proxy. The exact reverse-proxy configuration is application-specific and must preserve any required upgrade, streaming, header, and path behavior.

Expose the confirmed iii HTTP endpoint with Localtonet

Remote HTTP traffic reaches the confirmed local iii endpoint through a Localtonet tunnel.
Localtonet routes requests from a public endpoint through a tunnel to the verified local iii service.

Create remote access only after the local HTTP request succeeds. With Localtonet, our client on the iii host establishes an outbound connection to a relay server. The tunnel then provides a public address without requiring inbound router port forwarding, firewall changes, VPN setup, or a public IP address.

Use an HTTP tunnel for a confirmed iii HTTP endpoint. Do not use the presence of queues, cron jobs, Functions, or internal Worker connections as a reason to expose unrelated ports. The smallest safe exposure is the specific HTTP service required by the remote client.

1

Install and run the Localtonet client

Run our client on the iii host or on another device that can reach the confirmed local HTTP service. Keep the iii project running during tunnel setup and testing.

2

Authenticate or select the correct device

Use the device-specific authentication token associated with the client that can reach iii. Never paste the token into documentation, source code, screenshots, or shared logs.

3

Select an available relay server

Choose a currently available server or region from our dashboard. Available server codes and regions can change and may vary, so obtain the value from the current product instead of copying a hardcoded example.

4

Create an HTTP tunnel to the local iii service

Enter the exact local IP address and port confirmed during local verification. The tunnel targets the local service origin, while the route remains part of the HTTP request. Choose the required HTTP process type from the options currently available in the dashboard.

5

Start the tunnel

Creating a tunnel does not start it. Use the Start button and wait until the selected client and tunnel are connected before testing the assigned public HTTPS address.

6

Test the public route and stop it when finished

Append the verified iii route to the assigned public address and repeat the same method, headers, authentication, and request body used in the local test. Stop or delete the tunnel when remote access is no longer required.

For the current dashboard workflow and field descriptions, consult our Localtonet HTTP tunnel documentation. Dashboard options, relay availability, and plan-specific capabilities should always be checked at setup time rather than inferred from an example.

A tunnel has two independent lifecycle dependencies

The iii service must remain running and reachable from the Localtonet client, and the Localtonet tunnel must also be started with its selected device connected. If either side stops, the public endpoint will not be available.

Public reachability is not application authorization

A public HTTPS URL makes the selected service reachable, but it does not add application-specific identity or permission checks to an unauthenticated iii endpoint. Require appropriate authentication, validate input, apply least privilege, avoid exposing administrative surfaces, and publish only the route needed by the intended remote users or systems.

Generated, selected, and custom addresses

Localtonet HTTP tunnels can use a generated subdomain, a selected subdomain where supported, or a custom domain. All serve content at a public HTTPS address. Availability can depend on the current dashboard and subscription. Exact custom-domain DNS instructions are not included here because they must be checked against current documentation rather than guessed.

Routine operations and security practices

A reliable self-hosted backend needs more than a successful first request. Keep the project configuration, engine version, SDK versions, endpoint inventory, and tunnel target documented together. This makes it easier to distinguish an application change from a tunnel change during an incident.

Starting and checking the project

Run iii compose --up from the project root containing the relevant worker-compose.yaml. Observe the startup output and repeat the local endpoint test before relying on remote access. If the project is deployed with generated Docker assets, use the Docker Compose workflow established for that deployment and inspect container state and output when a service fails.

The supplied evidence does not define a dedicated iii shutdown command for the foreground Compose workflow. Use the normal process-management method for the environment in which you started it, and allow processes to terminate cleanly. For Docker deployments started with detached Compose, use your approved Docker Compose operational procedure rather than deleting containers or generated files without understanding their state.

Control configuration changes

Treat worker-compose.yaml, generated Docker assets, and environment configuration as operational code. Review changes, avoid committing credentials, and record why a new Worker or transport surface was enabled. If you regenerate Docker assets, remember that iii does not overwrite existing generated files. A successful generator run therefore does not mean your edited files were updated to new template contents.

Limit network exposure

Do not publish the SDK WebSocket, REST API, stream API, console, and every Worker endpoint by default. Determine which interface the remote user actually needs. Internal Worker connectivity and administrative observability usually have a different risk profile from a narrow application endpoint.

Bind local services according to your deployment model. When the Localtonet client runs on the same host, a loopback target can reduce unnecessary LAN exposure if iii supports and is configured for that binding. When our client runs on another device, the service must be reachable from that client over the local network. In either case, test from the client's actual network context before creating the tunnel.

Protect authentication material

The Localtonet device token identifies the client device and must remain secret. Store it only through the supported client workflow, rotate or replace it if exposed, and keep it out of repositories and support captures. Apply the same discipline to application secrets, generated .env files, queue credentials, and any API credentials used by iii Workers.

Revalidate after upgrades

After changing the engine or an SDK, verify the minor-version alignment, start the project locally, inspect Worker registration, test the HTTP route, and only then test the public URL. This order prevents a runtime compatibility issue from being misdiagnosed as a relay or DNS problem.

Troubleshooting the complete workflow

iii --version does not run

Return to the installation layer. Check the installer's output, current user, shell startup files, executable permissions, and command search path. Open a new shell if the installer changed environment configuration. Do not guess the binary location because the available evidence does not specify one universal path.

The project directory already exists or initialization is incomplete

Avoid initializing over an important directory without understanding the CLI's behavior for existing content. Choose a new project name or inspect the existing directory first. If initialization was interrupted, compare its contents with what the current command should generate and recreate it in a clean directory when safe. Do not manually invent missing configuration fields.

iii compose --up fails immediately

Read the first meaningful startup error rather than focusing on later cascading failures. Confirm that you are in the project root, that worker-compose.yaml exists, and that every declared Worker has its required runtime and configuration. Compare the installed engine minor version with the SDK versions used by the project.

The engine starts but no HTTP endpoint responds

Confirm that an HTTP Trigger is actually declared. iii can run Workers, Functions, queue consumers, schedules, and other capabilities without exposing an HTTP route. Inspect the generated files and runtime output for the actual listener and route. If the request reaches the listener but returns an error, verify the method, path, payload, content type, and authentication requirements.

The Docker REST port responds but the desired route does not

Port 3111 is documented as the REST API surface in the generated Docker deployment, but a listening port does not guarantee that every desired application route exists. Verify Worker startup and Trigger registration. Also check whether the route includes a prefix such as the path expected by the generated deployment configuration.

Local testing succeeds but the Localtonet public URL fails

Verify that the Localtonet client is connected on the selected device, the tunnel is started, and the tunnel targets the same local IP address and port that passed the local test. If our client runs in a container or on another device, 127.0.0.1 may refer to that container or device rather than the iii host. Use an address that is genuinely reachable from the client while keeping exposure as narrow as possible.

Then test the exact public path. If the local endpoint is /api/example, requesting only the public origin may not exercise that Trigger. Reproduce the same HTTP method, headers, body, and credentials used in the successful local request.

The public URL works until a terminal closes

Check both lifecycles. The terminal may have been running iii compose --up, or the Localtonet client may have disconnected. The project, required Workers, our client, and the tunnel all need to remain active for remote access to continue.

WebSocket or stream behavior differs from ordinary HTTP

iii's documented Docker deployment separates REST, stream, and SDK WebSocket services across ports 3111, 3112, and 49134. Do not assume that tunneling the REST port also forwards the other two surfaces. If your application requires multiple transports on one public origin, use a locally verified reverse proxy that routes each path to the correct port, then expose the reverse proxy. Confirm current Localtonet behavior and application compatibility before relying on long-lived or upgraded connections.

The console is available publicly by mistake

Stop the tunnel or correct its local target immediately. The console is an operational interface for logs, traces, Workers, Functions, Triggers, queues, and runtime state. It should not be exposed merely to test connectivity. Review whether sensitive operational information was reachable and apply your incident-response process if necessary.

Frequently asked questions

What is the current documented command for installing iii?

The current installation documentation uses curl -fsSL https://install.iii.dev/iii/main/install.sh | sh. After it finishes, run iii --version and confirm that a version number is returned. Review remote scripts before execution when required by your security policy.

How do I create and start a new iii project?

Run iii project init myapp, enter the generated directory with cd myapp, review its README and worker-compose.yaml, and start it with iii compose --up. The project is usable only when the engine and required Workers start successfully.

Which port does a new iii project use for HTTP?

The current installation and basic project-init evidence does not establish one universal HTTP port for every generated project. Find the actual hostname, port, and route in the generated configuration, README, and startup output. The optional generated Docker deployment documents port 3111 for its REST API, but that should not be assumed for every local workflow.

Is iii fully open source?

iii uses multiple licenses. Its engine is under the Elastic License 2.0, which is not an OSI-approved open-source license. Its CLI, SDKs, console, and documentation are under Apache License 2.0. Review the terms that apply to the components you plan to use.

Should the iii engine and SDK have exactly the same version?

Their patch versions can differ within the same minor line. iii recommends keeping the engine and SDKs on the same minor version, such as 0.11.x, unless a release note states otherwise.

Why should I test iii locally before creating a Localtonet tunnel?

Local verification proves that the engine, Worker, HTTP Trigger, route, and application logic work before another network layer is introduced. If the endpoint already fails locally, changing tunnel settings will not repair the underlying iii configuration.

Does an HTTP tunnel expose all iii transport ports?

No. An HTTP tunnel points to the local IP address and port you configure. In the generated Docker deployment, the REST API, stream API, and SDK WebSocket use separate documented ports. If an application needs several surfaces on one origin, first configure and verify a suitable local reverse proxy.

Does Localtonet keep the iii service online after iii stops?

No. Our tunnel forwards traffic to the local service. The iii project and its required Workers must remain active, our client must remain connected, and the tunnel must be running. Stopping any required part makes the public endpoint unavailable.

Make your verified iii endpoint available remotely

Once your iii HTTP route responds correctly on the local machine, use Localtonet to create an outbound HTTP tunnel without configuring inbound router port forwarding. Select the correct client device, target only the confirmed local service, start the tunnel, and protect the published application with appropriate authentication and least-privilege access.

Get Started Free โ†’

Localtonet is a secure multi-protocol tunneling and proxy platform designed to expose localhost, devices, private services, and AI agents to the public internet supporting HTTP/HTTPS tunnels, TCP/UDP forwarding, mobile proxy infrastructure, file server publishing, latency-optimized game connectivity, and developer-ready AI agent endpoint exposure from a single unified control plane.

support