# Step 7 — Build, Run & Deploy

You have a complete Tasks API: routed endpoints, real Postgres queries, typed errors, auth middleware, and a locked JSON contract. Let's ship it.

## Debug vs release builds

```bash
cargo run                # debug build — fast to compile, slower to run
cargo build --release    # optimized build — slow to compile, fast to run
./target/release/tasks-api
```

Always deploy the **release** build. The optimizer makes a large difference for a server, and the binary is self-contained.

## Observability: logs you can actually use

Before production, add structured logging with [`tracing`](https://github.com/tokio-rs/tracing). Unlike `println!`, it produces structured, level-filtered, span-aware output:

```toml
# Cargo.toml
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
```

```rust
#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter("info,tasks_api=debug")
        .init();

    tracing::info!("starting tasks-api");
    // ...
}
```

You already used `tracing::error!(?e, ...)` in the error handler in Step 6 — now it's captured. In production, `tracing` feeds log aggregators and error trackers (iKanban wires it into Sentry). The `?e` syntax records the value as a structured field, not just text.

## Configuration from the environment

Never hardcode secrets. Read them from the environment (a container platform injects them):

```rust
let database_url = std::env::var("DATABASE_URL")
    .expect("DATABASE_URL must be set");
let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
```

## Containerizing: the multi-stage Dockerfile

The standard way to deploy a Rust service is a **multi-stage** Docker build: compile in a full toolchain image, then copy just the binary into a tiny runtime image.

```dockerfile
# ---- build stage ----
FROM rust:1.86 AS builder
WORKDIR /app
COPY . .
ENV SQLX_OFFLINE=true          # use the committed .sqlx/ cache — no DB at build time
RUN cargo build --release

# ---- runtime stage ----
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/tasks-api /usr/local/bin/tasks-api
EXPOSE 3000
CMD ["tasks-api"]
```

The result is a small image containing a single static-ish binary — no interpreter, no `node_modules`, no JVM. This is one of Rust's operational joys: **the deploy artifact is tiny and starts instantly.**

> **`SQLX_OFFLINE=true` is doing real work here.** The build image has no database, so the `query!` macros read the committed `.sqlx/` cache instead of connecting. This is why Step 5 insisted you commit that directory — without it, the Docker build fails.

## A pre-deploy checklist

Before shipping a Rust backend, run the same gates a production team runs:

```bash
cargo fmt --check        # formatting is consistent
cargo clippy -- -D warnings   # zero lint warnings (treat warnings as errors)
cargo check              # everything type-checks
cargo test               # tests pass
```

Then, if you changed any `query!`/`query_as!` macro:

```bash
cargo sqlx prepare       # regenerate the offline cache
git add .sqlx/           # commit it alongside your change
```

> **Batch your deploys.** Compiling and deploying a Rust service takes minutes, not seconds. iKanban's workflow is explicit about grouping related backend changes into **one** push, because each push triggers a multi-minute build-and-deploy. Plan the change, make it complete, then ship once.

## Where a real backend goes from here

You've built and shipped a working Rust API. The same four ideas from Step 2 scale all the way up. When you're ready for production concerns, the ecosystem has mature answers:

| Need | Crate(s) |
|---|---|
| Rate limiting | `tower_governor` |
| Caching | `moka` |
| Auth / JWTs | `jsonwebtoken` |
| Cloud SDKs (S3, email, KMS) | the official `aws-sdk-*` family |
| Payments | `async-stripe` |
| Metrics & tracing | `tracing`, `sentry` |
| Background jobs | `tokio::spawn`, task queues |

Every one of these is in iKanban's own `Cargo.toml` — the service you use is built from exactly the pieces this tutorial introduced.

## You made it

You now understand, end to end, how a Rust backend is built:

1. **Why Rust** — safety and predictable performance without a GC.
2. **Four core ideas** — ownership, `Result`/`Option` + `?`, traits, async.
3. **Project setup** — Cargo, dependencies, workspaces.
4. **Axum** — routers, handlers, extractors.
5. **SQLx** — pools, parameterized queries, compile-time checking.
6. **Errors, auth, contracts** — `IntoResponse`, middleware, Serde + ts-rs.
7. **Shipping** — release builds, tracing, Docker, deploy discipline.

The best next step is to open a real handler in a codebase and read it — you now have the vocabulary to follow every line. Happy building.
