Deploy to production
Goal: get a Loco app running on a production host. Loco compiles to a single self-contained binary — the target server needs neither cargo nor a Rust toolchain, just the binary and a config/ folder.
1. Build the release binary
cargo build --releaseYour binary name matches the [package] name in Cargo.toml (with a -cli suffix, e.g. myapp-cli), and lands in ./target/release/.
2. Generate a Dockerfile (optional)
cargo loco generate deployment dockerkind is a positional argument — docker or nginx, not a --kind flag.
This writes two files to your project root:
Dockerfile— multi-stage build: compiles withcargo build --releasein arust:slimbuilder stage, then copies just the compiled binary andconfig/into a slimdebian:bookworm-slimruntime image. If your app has afrontend/package.json(client-side rendering), it also installs Node and runsnpm install && npm run buildin the builder stage. Ifserver.middlewares.static_assetsis configured, the folders it points to are copied into the final image too..dockerignore— excludestarget/,.git, and other build artifacts from the Docker build context.
Build and run it like any other image:
docker build -t myapp .docker run -p 5150:5150 --env-file .env myapp3. Generate an nginx config (optional)
cargo loco generate deployment nginxThis writes nginx/default.conf, a reverse-proxy config derived from your current server.host / server.port (config/<env>.yaml) — it proxies both the bare domain and wildcard subdomains to your app.
4. Review production config
There’s no separate “production mode” — Loco picks a config file by environment (config/production.yaml by default, or override with LOCO_ENV). Before deploying, walk through these sections:
Logger — turn pretty_backtrace off (it’s development-friendly, not performance-friendly) and prefer json for log aggregation:
logger: enable: true pretty_backtrace: false level: info format: jsonSee Configure logging for the full picture.
Server — bind to all interfaces and inject the port from the environment:
server: port: {{ get_env(name="NODE_PORT", default=5150) }} host: {{ get_env(name="APP_HOST", default="http://localhost") }}Database — real connection limits, no destructive flags:
database: uri: "{{ get_env(name='DATABASE_URL', default='postgres://loco:loco@localhost:5432/loco_app') }}" enable_logging: false connect_timeout: 500 idle_timeout: 500 min_connections: 1 max_connections: 10 auto_migrate: true dangerously_truncate: false dangerously_recreate: falseAuth secret — inject via environment, never hardcode:
auth: jwt: secret: "{{ get_env(name='JWT_SECRET') }}" expiration: 604800Queue / mailer — same pattern: point uri/host at env vars. See the Configuration reference for every key across all of these sections.
5. Run loco doctor before going live
myapp-cli doctor --productiondoctor validates DB/cache/queue connectivity against the config it would actually load. Add -c/--config to also print the fully-resolved config for inspection:
myapp-cli doctor --config --production6. Ship it
Copy the binary and the config/ folder to the server (no source, no Cargo.lock, no toolchain needed):
scp target/release/myapp-cli config/ user@server:/opt/myapp/ssh user@server '/opt/myapp/myapp-cli start'Verify
myapp-cli doctor --productionexits 0 and reports all checks passing.myapp-cli startboots and the startup banner shows the environment, DB, and logger you expect.- Hitting the app’s health/root route through nginx (if you generated one) returns a response, confirming the reverse proxy is wired to the right host/port.
Reference
generate deploymentCLI shape (docker/nginxaskind): CLI reference- Every config key referenced above (
logger,server,database,auth,mailer,queue): Configuration reference