While deploying to production is easy with Shuttle, running your project locally is useful for development. To start your project on your local system, while in your project directory, run:

shuttle run

The Shuttle CLI builds and runs your app in a similar way that the Shuttle platform does. It then starts a local provisioner server that simulates resource provisioning on the local system using Docker.

The environment variables available in the deployment environment are also set during a local run.

Local runs with databases

If your project uses a database resource, it will default to starting a local Docker container for that database. If you’d like to opt out of this behavior and supply your own database URI, simply pass it in as an argument to your resource. This argument also supports insertion of secrets from Secrets.toml with string interpolation:

#[shuttle_runtime::main]
async fn main(
    #[shuttle_shared_db::Postgres(
        local_uri = "postgres://postgres:{secrets.PASSWORD}@localhost:16695/postgres"
    )] pool: PgPool,
) -> ShuttleAxum { ... }
IMPORTANT: If Docker isn’t started, you may receive an “os error 2” error. This is typically related to your Docker installation. If you’re using Docker via the CLI, you can use any Docker command to start it up. If you’re using Docker Desktop, you will need to start Docker Desktop.

Expose your application to your local network

If you’d like to expose your application to you local network, for example if you’re serving a static website and you’d like to open it on your phone, simply pass in the --external flag:

shuttle run --external

This will bind your local application to 0.0.0.0:8000, and you will now be able to connect to it using your computer’s local IP. If you’d also like to change the port, you can do so with the --port argument:

shuttle run --external --port 8123
You may need to open the port your app is started on in your firewall.

Development Tips

Here are some small tips and tricks to boost your productivity when developing locally.

Live reload backend with cargo watch

To live reload your Shuttle app when you save a file, you can use cargo-watch:

# This will execute `shuttle run` when you save a file.
cargo watch -s 'shuttle run'
# This will also (q)uietly (c)lear the console between runs.
cargo watch -qcs 'shuttle run'

# There are many other helpful options, see `cargo watch --help`

Small caveat: Be aware that ignoring files with .ignore will also affect the behaviour of shuttle deploy.

Live reload frontend with tower-livereload

Using cargo watch will only reload the “backend” Shuttle process. If you are developing a frontend in the browser that is hosted by your Shuttle app, you might also want the web page to reload when files change. If you are using Axum or other Tower-compatible frameworks, the Tower layer tower-livereload can help you.

First, add it to your project:

cargo add tower-livereload

Then, when constructing your Axum router, add the layer like this:

let router = Router::new()
    .route(/* ... */)
    .layer(tower_livereload::LiveReloadLayer::new());

This will inject HTML responses with code for live reloading your web page when the server restarts. This also works in combination with cargo watch!

If you want to exclude this functionality from the release build, add this:

let mut router = /* ... */;

if cfg!(debug_assertions) {
    router = router.layer(tower_livereload::LiveReloadLayer::new());
}

Docker engines

cargo-shuttle uses the bollard crate to interact with the Docker engine on local runs.

If you are using a non-standard Docker engine, you might get this error:

got unexpected error while inspecting docker container:
error trying to connect: No such file or directory

The error is emitted due to bollard not connecting to the correct Docker Socket location.

On Unix, bollard defaults to connecting to unix:///var/run/docker.sock unless the DOCKER_HOST env variable overrides it.

If you end up using a DOCKER_HOST like below, you can add the export DOCKER_HOST=... line to your shell’s config file to have it automatically set in new shell sessions.

Docker Desktop

export DOCKER_HOST="unix://$HOME/.docker/run/docker.sock"

Podman

You will need to expose a rootless socket for Podman, and then set the DOCKER_HOST environment variable:

podman system service --time=0 unix:///tmp/podman.sock
export DOCKER_HOST=unix:///tmp/podman.sock

Colima

export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"