One Command to Start My Entire Local Development Stack with Tilt

Starting a modern application locally is rarely just a matter of running one process. My development environment includes a Vue web application, a .NET API backend, and a Docker-based environment that supports integration testing. Each part has its own command, dependencies, logs, and startup order. Tilt gives me one entry point for all of it:

tilt up

That single command bootstraps the services I need, keeps their status and logs in one place, and turns a multi-step setup into a repeatable development workflow. In this post, I will explain what Tilt is, how I use it across Vue, .NET, and Docker, and why it has become the front door to my local environment.

What Is Tilt?

Tilt is a development environment orchestration tool designed for applications made up of multiple services. It watches files, runs local commands, builds container images, deploys resources, and presents the state of the environment in a browser-based interface. Although Tilt is frequently associated with Kubernetes development, it can also manage Docker Compose resources and ordinary processes that run directly on the host.

The environment is defined in a file named Tiltfile. A Tiltfile is written in Starlark, a small language based on Python. Rather than keeping startup knowledge in a wiki page or a collection of shell commands, the Tiltfile makes the development environment executable and version-controlled.

When I run tilt up, Tilt evaluates the Tiltfile and starts the resources defined there. A resource is simply a unit of work that Tilt manages: a frontend server, an API, a database, a container, a setup task, or even a manually triggered integration-test command.

The Problem Tilt Solves for Me

Without an orchestrator, starting my application would mean opening several terminal windows and remembering the correct sequence:

  1. Install or restore frontend dependencies.
  2. Start the Vue development server.
  3. Restore, build, and run the .NET API.
  4. Start the required Docker services.
  5. Wait for databases and supporting services to become healthy.
  6. Keep track of logs across every process.
  7. Remember which commands to stop when I am finished.

None of those steps is particularly difficult on its own. The friction comes from repetition and inconsistency. It is easy to skip a service, use a slightly different command, or start integration tests before their dependencies are ready. Tilt gives the entire team a common startup command and a shared definition of a healthy local environment.

My Local Stack

My Tilt environment combines three different kinds of workloads:

  • Vue web application: a long-running local development server with hot module replacement.
  • .NET API: an ASP.NET Core backend running locally so I can debug it with familiar .NET tooling.
  • Docker integration environment: databases and supporting infrastructure used by integration tests.

This hybrid model is one of Tilt’s strengths. A service does not have to run in Kubernetes—or even in a container—to participate. Tilt can treat local processes and containerized infrastructure as peers in the same dashboard.

A Simplified Tiltfile

The exact commands and paths will vary by repository, but the following example illustrates the shape of the setup:

# Tiltfile

# Start the Vue development server.
local_resource(
    'web',
    serve_cmd='pnpm --dir ./web dev',
    deps=['./web/src', './web/package.json', './web/pnpm-lock.yaml'],
    labels=['application']
)

# Start the ASP.NET Core API.
local_resource(
    'api',
    serve_cmd='dotnet watch --project ./api/MyApp.Api.csproj run',
    deps=['./api'],
    labels=['application']
)

# Start containerized dependencies for integration testing.
docker_compose('./docker-compose.integration.yml')

dc_resource('sql-server', labels=['integration'])
dc_resource('wiremock', labels=['integration'])

# Keep the full test suite manual so startup remains fast.
local_resource(
    'integration-tests',
    cmd='dotnet test ./tests/MyApp.IntegrationTests.csproj',
    resource_deps=['sql-server', 'wiremock', 'api'],
    trigger_mode=TRIGGER_MODE_MANUAL,
    auto_init=False,
    labels=['tests']
)

The Vue and .NET processes use serve_cmd because they are long-running servers. The Docker Compose file defines the containerized integration-test dependencies. The test suite is represented as another resource, but it is configured for manual execution so that bringing up the environment does not automatically run every test.

This is intentionally a simplified example. In a real repository I can add environment variables, readiness probes, port links, dependency relationships, ignored paths, and per-developer configuration.

Controlling Startup Order and Readiness

Starting processes is only half the job. A dependent resource should not start merely because another process has been launched; it should start when that process is actually ready. Tilt supports resource dependencies through resource_deps, and local resources can use readiness probes based on HTTP, TCP, or an executable command.

For example, I can make Tilt wait for the API health endpoint before treating the API as ready:

local_resource(
    'api',
    serve_cmd='dotnet watch --project ./api/MyApp.Api.csproj run',
    deps=['./api'],
    readiness_probe=probe(
        http_get=http_get_action(port=5000, path='/health')
    )
)

That small distinction matters for integration testing. It reduces timing-related failures and replaces arbitrary sleep commands with a meaningful signal that the service is available.

A Better Feedback Loop

After startup, Tilt continues to manage the development loop. It watches the dependencies declared for each resource and responds to relevant changes. My Vue server can continue using its normal hot-reload behavior, while dotnet watch rebuilds or restarts the backend when C# code changes. Container images or local tasks can be rebuilt only when the files they depend on change.

The Tilt UI is especially useful when several services are running. It provides one resource list with status, build output, and runtime logs. Instead of searching through terminal tabs to find the process that failed, I can select the affected resource and immediately see its output. A red resource tells me where to begin; a green environment gives me confidence that the stack is ready.

Why I Prefer This to a Large Startup Script

A shell script can start several commands, but Tilt adds orchestration concepts that become increasingly valuable as the system grows:

  • Resource awareness: each service has its own status and logs.
  • File watching: updates can be scoped to the files that matter.
  • Dependencies: startup order can be declared instead of implied.
  • Readiness: services can report when they are genuinely available.
  • Manual triggers: expensive tasks such as integration tests can remain one click or one command away.
  • Selective startup: Tilt can start all resources or a requested subset.
  • Shared configuration: the Tiltfile lives with the code and evolves through normal code review.

The result is a development environment that behaves more like a small, observable system and less like a collection of unrelated commands.

A Practical Daily Workflow

My normal workflow is straightforward:

# Start every resource defined by the Tiltfile
tilt up

# Or start only selected resources
tilt up web api

# Tear down Docker Compose or Kubernetes resources
tilt down

One operational detail is worth knowing: exiting tilt up stops long-running local resources created with serve_cmd, but Kubernetes and Docker Compose resources can continue running. The tilt down command removes those managed resources when I want a clean shutdown.

Tips for Introducing Tilt

  1. Start with the commands that developers already use. Wrap the existing Vue, .NET, and Docker commands before trying to redesign the entire environment.
  2. Name resources clearly. Names such as web, api, sql-server, and integration-tests make the UI self-explanatory.
  3. Declare narrow dependencies. Avoid watching generated files, build output, and dependency directories that can cause unnecessary updates.
  4. Add readiness probes. They are more reliable than fixed delays, especially for APIs and databases.
  5. Keep expensive actions manual. Full integration tests, database resets, and data seeding often work best with auto_init=False and manual trigger mode.
  6. Document prerequisites. Tilt simplifies orchestration, but developers may still need the Tilt CLI, the .NET SDK, Node.js or pnpm, and Docker installed.
  7. Keep the Tiltfile in source control. Treat changes to the development environment with the same care as application code.

The Payoff

The biggest benefit is not that Tilt eliminates every tool in my stack. Vue still uses its development server, .NET still uses its own build and watch commands, and Docker still runs the integration infrastructure. Tilt coordinates those tools and gives them a common interface.

For me, tilt up has become the one command that means “make this repository ready for development.” It reduces setup friction, makes failures easier to diagnose, and keeps the local workflow consistent as the application grows. If your project requires several terminals before you can change a line of code, Tilt is worth evaluating.

Leave a Comment

Your email address will not be published. Required fields are marked *