
Run a browser automation API locally, verify every component, and publish only the HTTP endpoint you need
Steel Browser packages browser automation infrastructure, a REST API, and a web interface into a self-hostable project for AI agents and applications. In this guide, we install Steel Browser with its documented Docker options, verify the API and UI locally, explain persistence and routine container operations, and troubleshoot common startup problems. After the local deployment works, we connect its HTTP service to a public HTTPS address with Localtonet. The tunnel is treated as a separate deployment step so that Docker or Steel problems are resolved before remote access is introduced.
๐ What's in this guide
What Steel Browser provides and how the Docker deployment works
Steel Browser is an open-source browser API designed for applications and AI agents that need to interact with the web programmatically. It manages browser sessions, pages, browser processes, cookies, local storage, and lifecycle cleanup. Its project documentation also describes support for Puppeteer, Playwright, and Selenium through Chrome DevTools Protocol connections, along with browser-oriented tools for producing screenshots, PDFs, readability output, and Markdown.
Self-hosting gives an application a Steel endpoint under the operator's control. That can be useful during local development, private automation, testing, or integration work where an agent running elsewhere must reach a browser service on a workstation or server. Self-hosting also means that the operator is responsible for host capacity, updates, access control, network exposure, data handling, and monitoring.
Steel is identified by its maintainers as public beta software. Treat that status as an operational consideration. Pin and test releases before production use, review changes when upgrading, and do not assume that an interface or deployment behavior will remain unchanged across beta versions.
http://localhost:3000/ui. The split Compose deployment serves its UI on host port 5173.
http://localhost:3000/documentation.
Combined image versus split containers
The easiest documented local path is the combined image at ghcr.io/steel-dev/steel-browser:latest. It packages the API and UI together, serves both through port 3000, and uses port 9223 for debugging. This layout is the clearest match for this guide's Localtonet integration because one HTTP tunnel to port 3000 can reach the combined service and its /ui route.
The Docker Compose layout uses ghcr.io/steel-dev/steel-browser-api:latest for the API and ghcr.io/steel-dev/steel-browser-ui:latest for the UI. It publishes API port 3000, debugger port 9223, and UI port 5173. The containers communicate over a Docker bridge network named steel-network.
| Deployment | Local addresses | Best fit | Important behavior |
|---|---|---|---|
| Combined pre-built image | localhost:3000 and localhost:3000/ui |
Fast local setup and a simple HTTP tunnel target | API and UI run in one container; port 9223 is also mapped by the documented command |
| Split Docker Compose | API on localhost:3000; UI on localhost:5173 |
Separate API and UI container management | The API and UI communicate through the named Docker network |
| Locally built combined image | localhost:3000 and localhost:3000/ui |
Testing source changes in the combined build | The repository is cloned and the image is built locally before it is run |
| Development Compose file | Server on port 3000 and development UI on port 5173 | Contributor development with local source changes | The development images should be rebuilt when changes need to be reflected |
The split Compose deployment remains fully documented below, but its UI and API occupy different host ports. Exposing a split frontend can require application host configuration that is specific to the current Steel version. Because the supplied evidence does not establish every remote frontend setting, the Localtonet walkthrough targets the combined deployment's verified HTTP endpoint on port 3000.
Prerequisites for self-hosting Steel Browser
Steel's Docker documentation specifies Docker 20.10.0 or later, at least 4 GB of RAM, and 10 GB of free disk space. Docker Compose is also required if you select either Compose workflow. Current Docker installations commonly include the Compose plugin, which is invoked as docker compose.
Before installation, confirm that another application is not already occupying the host ports required by your selected layout. The combined image maps host ports 3000 and 9223. The split Compose deployment also maps host port 5173. If a port is already in use, Docker will normally report a binding or allocation error instead of starting the affected container.
You also need permission to run Docker and enough host capacity to launch Chrome. Browser workloads can consume substantial memory and CPU depending on the number and complexity of sessions. The documented 4 GB prerequisite is the minimum host requirement, not a guarantee that every workload will fit within that amount.
For the optional remote-access section, install the Localtonet client on the Steel host or another device that can reach its local HTTP endpoint. You also need a device-specific Localtonet authentication token. Tokens are sensitive and must not be included in screenshots, source repositories, Compose files, shell history examples, or shared logs.
Steel's HTTP endpoint can control browser sessions and may expose operational information through its UI or API documentation. Do not treat a public URL as an access-control mechanism. Decide which callers need access, apply authentication or another suitable authorization layer where supported by your deployment, and use least privilege. This guide does not claim that Steel's self-hosted HTTP service has a particular authentication default because the supplied installation evidence does not establish one.
Verify Docker and available resources
Run the following commands to confirm that Docker and the Compose plugin are available:
docker --version
docker compose version
Docker's version output should show 20.10.0 or later. If the second command fails and you intend to use only the combined image, you can continue with docker run. If you intend to use Compose, install or enable the Docker Compose plugin for your operating system before proceeding.
Resource and free-space checks differ by operating system, so use the host's normal administration tools to verify at least 4 GB of RAM and 10 GB of free storage. Account for additional storage if you expect Chrome cache data, extensions, logs, locally built images, or application output to grow over time.
Install Steel Browser with the combined Docker image
The combined pre-built image is the shortest documented path from an empty Docker host to a working Steel Browser instance. Docker pulls the image from GitHub Container Registry if it is not already present, starts the container interactively, maps the API and debugger ports, and removes the container when it stops.
Confirm the required ports are available
Make sure host ports 3000 and 9223 are not already assigned to another container or process. Port 3000 will serve the combined HTTP API and UI. Port 9223 is the Chrome debugging port and should not be published beyond the trusted host or network.
Run the official combined image
Start the documented image with the command below. The --rm option removes this container when it stops, and -it keeps the process attached to the current terminal.
Wait for the service to finish starting
Keep the terminal open and review the container output for startup errors. Do not create a public tunnel yet. First confirm that the HTTP server, UI, and health endpoint respond locally.
docker run --rm -it -p 3000:3000 -p 9223:9223 ghcr.io/steel-dev/steel-browser:latest
Open http://localhost:3000 on the Docker host after the service starts. The combined web UI is available at http://localhost:3000/ui, and the local REST API documentation is available at http://localhost:3000/documentation.
This documented quick-start container is attached to the terminal and includes --rm. Stopping the process removes that container. The image remains available locally, but the stopped container itself does not. Use this workflow for initial evaluation and verification. Before relying on the deployment operationally, decide how you will restart it and which data directories need persistence for your workload.
Build the combined image locally instead
If you need to inspect or modify Steel's source and then create your own local image, clone the repository, build the root Dockerfile, and run the resulting image. This is a different workflow from merely pulling the pre-built image.
git clone https://github.com/steel-dev/steel-browser.git
cd steel-browser
docker build -t steel-browser:local .
docker run --rm -it -p 3000:3000 -p 9223:9223 steel-browser:local
The locally built image uses the same documented host ports as the pre-built combined image. Verify it through http://localhost:3000 and http://localhost:3000/ui. Building from source offers control over the build, but it also transfers responsibility for reviewing the source revision, build output, dependencies, and updates to the operator.
Install Steel Browser with Docker Compose

The documented Compose quick start separates Steel into an API container and a UI container. The API container exposes ports 3000 and 9223 and stores Chrome data and extensions in a host-mounted .cache directory. The UI container maps its internal web server on port 80 to host port 5173. Both services join the steel-network bridge network.
Create and enter a deployment directory
Create a dedicated directory so that the Compose file and the mounted .cache directory remain together.
Create the Compose configuration
Save the documented configuration as docker-compose.yaml inside the new directory. Preserve the service names, port mappings, volume mount, and named network unless you understand the application-level consequences of changing them.
Launch the containers
Run docker compose up -d. Detached mode leaves the containers running in the background after the command returns.
Open the split web interface
Access the documented Compose UI at http://localhost:5173. Verify the API separately on host port 3000 before diagnosing any UI-to-API issue.
mkdir steel-browser && cd steel-browser
services:
api:
image: ghcr.io/steel-dev/steel-browser-api:latest
ports:
- "3000:3000"
- "9223:9223"
volumes:
- ./.cache:/app/.cache
networks:
- steel-network
ui:
image: ghcr.io/steel-dev/steel-browser-ui:latest
ports:
- "5173:80"
depends_on:
- api
networks:
- steel-network
networks:
steel-network:
name: steel-network
driver: bridge
docker compose up -d
Open http://localhost:5173 after both services start. The API remains reachable at http://localhost:3000, and its health endpoint can be checked at http://localhost:3000/api/health.
Persistence in the Compose layout
The Compose configuration maps the host directory ./.cache to /app/.cache in the API container. Steel documents this directory as storage for Chrome data and extensions. Because it is a bind mount, its content remains in the deployment directory independently of the API container's writable layer.
Check the ownership and permissions of .cache if Chrome cannot start or Steel reports file-access errors. Do not delete this directory casually if its Chrome data or extensions matter to your workflow. Conversely, do not assume that this cache mount is a complete backup of every possible piece of application state. The supplied evidence only identifies it as persistence for Chrome data and extensions.
Apple Silicon and development Compose workflows
Steel's repository README documents a platform override for Mac computers using Apple Silicon:
DOCKER_DEFAULT_PLATFORM=linux/arm64 docker compose up
Contributors who need local source changes reflected in the running services should use the development Compose file rather than the default production-oriented Compose file:
docker compose -f docker-compose.dev.yml up --build
The documented detached form is:
docker compose -f docker-compose.dev.yml up -d --build
In this development layout, the server runs on port 3000 and the UI on port 5173. The --build option rebuilds the images so that local source changes are incorporated.
Verify the local Steel Browser deployment

Verification should progress from container state to HTTP health, then to the UI and API documentation. This order makes failures easier to isolate. A Localtonet tunnel cannot repair a container that is stopped, an API that has not finished starting, or a local port that is bound incorrectly.
1. Confirm that the containers are running
For either deployment layout, list running containers:
docker ps
In the combined layout, expect the Steel container to publish ports 3000 and 9223. In the split layout, expect both the API and UI services. If a container is absent, inspect all containers, including stopped ones:
docker ps -a
A stopped container generally indicates a startup failure, a manual stop, or completion of the container's main process. Read its logs before recreating it so that the original error is not lost.
2. Check the API health endpoint
Steel's documented connectivity check is:
curl http://localhost:3000/api/health
Run this command on the Docker host. A successful HTTP response establishes that a service is listening on host port 3000 and that Steel's health route responds. If it fails, stay focused on Docker, Steel, and local networking. Do not proceed to Localtonet configuration until this check succeeds.
3. Open the appropriate UI address
For the combined image, open:
http://localhost:3000/ui
For the split Compose deployment, open:
http://localhost:5173
Loading the UI confirms that the frontend is being served. It does not by itself prove that every browser action works, so also verify that the UI can communicate with the API and that the host has enough resources to start Chrome.
4. Review the local REST documentation
On the combined deployment, open:
http://localhost:3000/documentation
This local documentation is useful for confirming the routes supported by the exact image you are running. Steel provides sessions and quick-action endpoints, but request schemas and supported options can evolve during beta. Use the documentation generated by the installed version rather than guessing request bodies from an unrelated release.
5. Perform a controlled functional test
After the health route and UI work, use the local interface or the installed version's API documentation to perform a small browser operation. Observe whether a session starts, whether Chrome remains running, and whether the action completes without resource or permission errors. Avoid pointing an initial test at sensitive accounts or production systems.
Port 9223 is the Chrome debugging service, not the Steel web UI. Steel's own production guidance says to avoid exposing this debugging port to the public internet. The Localtonet integration in this guide targets HTTP port 3000 only.
Routine operation, updates, and deployment choices
The exact commands used to operate Steel depend on whether it was launched as an attached one-off container or as a detached Compose project. Keep that distinction clear when documenting the deployment for other operators.
Stopping the combined quick-start container
The combined command runs interactively. Pressing the terminal's interrupt shortcut stops the process. Because the command includes --rm, Docker removes the container after it stops. Start a fresh container by running the same documented command again.
This behavior is convenient for evaluation but is not a complete service-management policy. For long-running use, define how the process will restart after host maintenance or failure, how persistent data will be stored, and how logs will be retained. The supplied Steel quick-start evidence does not prescribe a complete production orchestration configuration for the combined image, so this guide does not invent one.
Stopping and restarting the Compose project
From the directory containing docker-compose.yaml, use the standard Compose lifecycle:
docker compose stop
docker compose start
To stop and remove the project's containers and network, while leaving the host bind-mounted .cache directory in place, use:
docker compose down
Recreate the services with:
docker compose up -d
Before destructive maintenance, verify what data exists in the deployment directory and back up anything your workflow requires. Do not treat cache persistence as a substitute for a tested backup plan.
Inspecting logs
For Compose services, review aggregate logs with:
docker compose logs
Follow current output while reproducing a problem:
docker compose logs -f
To narrow output to a documented service name:
docker compose logs api
docker compose logs ui
A combined container can be inspected with docker logs if it was started in a way that leaves it available and you know its container name or ID. The official troubleshooting example uses docker logs steel-browser_api_1, but generated Compose container names can differ by project name and Compose version. Obtain the actual name from docker ps -a instead of assuming the example name.
Updating images carefully
Both documented quick-start configurations use the mutable latest image tag. That is convenient for evaluation, but Steel recommends specific image versions rather than latest for production environments. A specific version or immutable digest makes a deployment repeatable and prevents an unreviewed image change from being introduced merely because a container was recreated.
Before updating, record the current version or digest, review release notes, back up relevant data, and test the new image against your API clients and browser workflows. Steel is in public beta, so compatibility testing is especially important. The evidence supplied for this article confirms a beta release but does not establish that one particular release is the correct production version for every reader.
Expose Steel Browser over HTTP with Localtonet

Once the combined deployment responds at http://localhost:3000, Localtonet can expose that HTTP service without inbound router port forwarding, firewall changes, VPN setup, or a public IP address. Our client establishes an outbound connection from the selected device to a Localtonet relay server. The resulting HTTP tunnel provides a public HTTPS address while the selected client is connected and the tunnel is running.
This does not move Steel into our infrastructure or start Steel on your behalf. Docker continues to run Steel on your machine. Localtonet forwards public HTTP traffic to the local IP address and port configured for the tunnel.
Install the Localtonet client on the same host as Steel when possible for the simplest target. In that arrangement, the target is the local HTTP service on port 3000. If the client runs on another device, that device must be able to reach the Steel host over the local network, and the target must use the Steel host's reachable private address rather than blindly using localhost.
Install and run the Localtonet client
Install our client on the Steel host or on a device that can reach Steel's port 3000. Keep the client running because the tunnel is available only while the selected client or device remains connected and the tunnel is running.
Authenticate or select the device
Use the device-specific authentication token associated with the client that will carry the tunnel. Obtain the real token from your Localtonet account and never copy a placeholder token from an article or expose the value publicly.
Select an available relay server
Choose an available server or region shown in the current Localtonet dashboard. Available server codes and regions can vary, so this guide does not hardcode a value.
Create an HTTP tunnel to Steel
Create an HTTP tunnel and set its local target to the address that serves the verified Steel endpoint. When our client runs on the same machine, use the loopback address with port 3000. When it runs elsewhere, use a private address that is reachable from that client device.
Choose the HTTP process type
Select the appropriate available process type: Random Sub Domain, Custom Sub Domain, or Custom Domain. These process types serve the same local content at a public HTTPS address. Availability can vary, and exact custom-domain DNS requirements should be checked in the current dashboard and documentation rather than guessed.
Start and verify the tunnel
Creating a tunnel does not start it. Press Start, wait for the tunnel to run, and open the assigned public HTTPS address. Check the base route, append /ui for the combined UI, and verify only the API routes that your authorized clients require.
The same public origin should route paths such as /ui, /documentation, and /api/health to the combined service because they are all served through local HTTP port 3000. Confirm this against your installed Steel version rather than assuming that every future release preserves the same paths.
For the current product workflow and interface, consult our Localtonet HTTP tunnel documentation. Do not copy server codes, device tokens, or endpoint values from another deployment because these values are specific to the current account, device, and available infrastructure.
If the public address fails, run curl http://localhost:3000/api/health on the Steel host again. If that local request fails, troubleshoot Steel or Docker. If it succeeds, confirm that the selected Localtonet client is connected, the tunnel is started, and the configured local address is reachable from that client device.
Security considerations for a remotely reachable browser API
A browser automation service is a sensitive capability. An authorized caller may be able to create browser sessions, visit websites, retain cookies or local storage, inspect output, and consume host resources. The right security model depends on the installed Steel version, the calling applications, and the surrounding infrastructure, but several boundaries apply to this workflow.
Expose only the intended HTTP service
Point the Localtonet HTTP tunnel to port 3000 for the combined deployment. Do not create a public tunnel to port 9223. Chrome DevTools Protocol access can provide deep control over a browser and Steel explicitly recommends avoiding public exposure of its debugging port.
The documented Docker command maps 9223 to the Docker host. Review the host's network perimeter so that this mapping is not reachable from untrusted networks. A Localtonet tunnel does not automatically make every host port public, but other firewall, cloud security-group, router, or host-binding rules still matter.
Require meaningful authorization
A difficult-to-guess tunnel URL is not a replacement for authentication. Apply an authentication and authorization control appropriate to the Steel version and deployment, restrict clients to the minimum capabilities they need, and avoid sharing one powerful credential among unrelated agents. The provided installation evidence does not define Steel's exact self-hosted authentication behavior, so verify the installed release before allowing untrusted traffic.
Protect secrets and browser state
Browser sessions can contain cookies, local storage, downloaded files, screenshots, page content, or credentials entered into websites. Avoid logging secret-bearing request bodies, redact operational logs before sharing them, and limit access to persistent directories. Never place Localtonet device tokens, application API keys, or website credentials in public repositories.
Control resource consumption
Browser sessions consume CPU, memory, disk, and network resources. Set operational limits appropriate to the host and monitor for runaway session creation. Steel recommends configuring resource limits for production deployments, but the correct values depend on workload and host capacity. Do not copy a sample memory value without load testing your own browser tasks.
Pin and review production images
Mutable latest tags are useful for a quick start but weaken reproducibility. Use a reviewed image version or digest for production, test upgrades before rollout, and maintain a rollback path. Also review the Steel project's release notes because it is public beta software and browser compatibility changes can affect automation behavior.
| Surface | Risk | Recommended treatment |
|---|---|---|
| Steel HTTP API on port 3000 | Remote creation or control of browser workloads | Expose only when required and place suitable authentication and authorization in front of sensitive operations |
| Steel UI and API documentation | Operational visibility and discovery of supported routes | Limit access to authorized users rather than relying on URL secrecy |
| Chrome debugger on port 9223 | Powerful low-level browser control | Keep it private and never use it as the public Localtonet HTTP target |
| Chrome data and extensions | Potentially sensitive persisted browser state | Protect the cache directory with host permissions and include relevant data in a deliberate backup policy |
| Localtonet device token | Identification of the client device used for tunnels | Keep it secret and obtain it only from the current account or dashboard |
| Mutable container tags | Unreviewed application changes during recreation | Use tested, specific image versions or immutable digests for production |
Troubleshooting Steel Browser, Docker, and the HTTP tunnel
Docker reports that a port is already allocated
Another process or container is using port 3000, 5173, or 9223. Inspect running containers with docker ps and check the host's listening ports with the operating system's normal networking tools. Stop or reconfigure the conflicting service before retrying. If you change Steel's host port mapping, remember that the Localtonet target must use the resulting host port, while the container-side port must still match the service listening inside the container.
Chrome will not start
Steel's documentation identifies insufficient memory, missing shared libraries, ARM architecture issues, and permission problems with the .cache directory as common causes. Confirm the host meets the 4 GB RAM prerequisite, inspect API logs, verify the host architecture and image compatibility, and check ownership and write access for the mounted cache directory.
For Compose, begin with:
docker compose logs api
On Apple Silicon, use the documented architecture override if needed:
DOCKER_DEFAULT_PLATFORM=linux/arm64 docker compose up
The UI loads but cannot connect to the API
First verify that both Compose services are running:
docker compose ps
Then check the API directly from the host:
curl http://localhost:3000/api/health
Steel also documents checking connectivity from the UI container to the API service:
docker exec steel-browser_ui_1 curl http://api:3000/api/health
Replace steel-browser_ui_1 with the actual UI container name shown by docker ps. If the host health check works but the container-to-container check fails, inspect the Compose network, service names, and whether both containers joined steel-network.
The health endpoint works, but the UI route does not
Confirm that you are using the correct address for the selected layout. The combined image serves the UI at http://localhost:3000/ui. The split Compose deployment serves the UI at http://localhost:5173. Opening /ui
The public Localtonet URL does not respond
Retest http://localhost:3000/api/health on the Steel host. If it fails, inspect Steel before changing the tunnel. If it succeeds, verify that the selected Localtonet device is connected, that the correct device token was used, that the tunnel was explicitly started, and that its target points to port 3000.
When our client runs on another machine, localhost refers to that client machine, not the Steel host. Configure the Steel host's reachable private IP address and confirm that the client device can connect to that address and port over the LAN.
The public base address works, but a client call fails
Confirm that the client uses the assigned HTTPS origin and the correct route from the installed Steel API documentation. Check for hardcoded references to localhost:3000 in the calling application. SDKs for self-hosted Steel require their base URL setting to reference the self-hosted endpoint, but the exact option depends on the language and SDK version. Steel's README identifies baseURL for Node and base_url for Python; consult the installed SDK's reference before changing application code.
Also check whether the client requires a WebSocket or long-lived connection and whether the installed application reports an origin, authentication, or host error. The evidence provided for this guide does not establish every Steel remote-host configuration variable, so use the exact error and installed-version documentation rather than inventing an environment variable.
The service disappears after closing the terminal
The combined quick-start command runs interactively. Closing or interrupting its terminal stops the process, and --rm removes the container. Use the detached Compose deployment for the documented background workflow, or design an appropriate long-running container policy after evaluating persistence, restart, and logging requirements.
Local changes do not appear in the development deployment
Use the development Compose file with --build:
docker compose -f docker-compose.dev.yml up --build
The default Compose file pulls pre-built API and UI images. It is not the documented contributor workflow for rebuilding local source changes.
Frequently asked questions
What is the simplest way to self-host Steel Browser?
Run the documented combined image with docker run --rm -it -p 3000:3000 -p 9223:9223 ghcr.io/steel-dev/steel-browser:latest. It serves the application at http://localhost:3000 and the UI at http://localhost:3000/ui. This quick-start container is removed when it stops because the command includes --rm.
What resources does Steel Browser require?
The documented prerequisites are Docker 20.10.0 or later, at least 4 GB of RAM, and 10 GB of free disk space. Actual browser workloads may need more CPU, memory, and storage depending on session concurrency and the sites being automated.
Which Steel Browser port should I expose with Localtonet?
For the combined image used in this guide, create an HTTP tunnel to local port 3000. That port serves the API and the /ui route. Do not expose port 9223, which is the Chrome debugging port.
Does creating a Localtonet tunnel immediately make Steel public?
No. Creating a tunnel does not mean it is running. You must start it. The assigned endpoint remains available only while the selected Localtonet client is connected and the tunnel is running.
Can the Localtonet client run on a different machine from Steel Browser?
Yes, if the client device can reach the Steel host's HTTP port over the local network. In that layout, use the Steel host's reachable private IP address as the tunnel target. Do not use localhost, because it would refer to the separate Localtonet client device.
Where is the Steel Browser API documentation after installation?
The combined local deployment provides its REST API documentation at http://localhost:3000/documentation. Use that documentation to confirm request schemas and routes for the exact version you installed.
Does Docker Compose preserve Steel Browser data?
The documented Compose configuration mounts ./.cache on the host to /app/.cache in the API container. Steel identifies this location as storage for Chrome data and extensions. The evidence does not establish that this directory contains every kind of application state, so use a separate, tested backup plan for anything important.
Should I use the latest image tag in production?
Steel recommends specific image versions rather than latest for production. Pin a reviewed version or immutable digest, test it with your workloads, and retain a rollback path. This is particularly important while Steel remains in public beta.
Does a public HTTPS address automatically secure the Steel API?
No. HTTPS protects transport to the tunnel edge, but a public address is not authorization. Apply suitable authentication and access controls for the installed Steel version, protect browser state and credentials, and expose only the routes and capabilities required by authorized callers.
Connect your verified Steel Browser deployment with Localtonet
After Steel responds locally on port 3000, create an HTTP tunnel to give authorized applications a public HTTPS endpoint without configuring inbound router port forwarding or requiring a public IP address.
Get Started Free โ