# Deployment

> Build the App into one static binary with act build, run it in production, and package it in a container.

This guide covers deploying a Tinka App. You will learn how to:

- Build the App for production with `act build`
- Ship static files inside the binary
- Build for another operating system or architecture
- Run the binary in production
- Package the App in a container image

## Overview

A Tinka App builds into **one binary** that holds everything it needs: your code, Tinka, and any files you embed. There's nothing to install next to it, no runtime and no dependencies to copy, so deploying the App means copying one file and running it with its configuration in the environment.

`act build` runs `go build` with the settings a production binary needs, and prints the command it runs, so a machine without `act`, such as a CI runner or a container build, runs the same command with Go alone.

## Build the App

Run `act build` from the App's directory:

```sh
act build
```

```text title="Output"
$ GOWORK=off CGO_ENABLED=0 go build -trimpath -o bin/blog .
Built bin/blog (14.7 MB, v1.2.0, linux/amd64, revision 58d23ba966e3)
```

The binary is named after the last part of the App's module path and written to `bin/`. It is built with:

- `CGO_ENABLED=0`, so it's **static**: it runs on any machine of the same operating system and architecture, including minimal container images without a C library.
- `-trimpath`, so it holds no paths from the machine that built it, and the same commit builds the same binary anywhere.
- `GOWORK=off`, so a `go.work` file on your machine can't change what goes in.

The version comes from the git tag of the commit, such as `v1.2.0`, and ends in `+dirty` when the App has uncommitted changes. Go records it in the binary, along with the commit, so `act about` and your own code read it without a build step.

`act build` warns when `go.mod` replaces a module with a local directory, because that build differs from one made from the same commit on another machine, and when the App has uncommitted changes.

:::tip
Add `bin/` to `.gitignore`. Git counts files it doesn't track as changes, so a `bin/` it sees makes every build after the first one `+dirty`.
:::

## Ship static files

Files the App serves, such as stylesheets and images, go inside the binary with `embed`, so they always match the code that serves them:

```go title="assets.go"
package main

import (
	"embed"
	"io/fs"
	"net/http"

	"github.com/mucolabs/tinka"
)

//go:embed public
var public embed.FS

// assets serves the files in public/ under /assets/.
func assets(app *tinka.App) error {
	files, err := fs.Sub(public, "public")
	if err != nil {
		return err
	}
	app.Handle(http.MethodGet, "/assets/", http.StripPrefix("/assets/", http.FileServerFS(files)))
	return nil
}
```

`act serve` rebuilds the App when an embedded file changes, as it does for Go files.

## Build for another system

Pass `--target` to build for another operating system and architecture, such as a Linux server from a Mac:

```sh
act build --target linux/arm64
```

The first build for a new target takes longer, while Go builds its standard library for it; later builds are as fast as usual. Pass `--cgo` only when a dependency needs C, such as a SQLite driver written in C: the binary then needs the system's C library at run time.

## Run the App in production

Copy the binary to the server, and run it with its configuration in the environment. Production reads no `.env` file, so set every variable in your platform's settings or your service definition:

```sh
APP_ENV=production APP_KEY=your_app_key ./blog
```

Run with no arguments, the binary serves requests; with arguments, it runs one of the App's [Commands](guides/command-line/introduction.md), such as `./blog about`. It shuts down gracefully on `SIGTERM`, which is what platforms send when they replace an instance. See [Application lifecycle](guides/concepts/application-lifecycle.md) for the shutdown timeout and [Health checks](guides/digging-deeper/health-checks.md) for readiness probes and draining.

On a server managed with systemd, socket activation keeps the port open while the App restarts. See [Socket activation](guides/concepts/application-lifecycle.md#socket-activation).

## Package the App in a container

A container build has Go but not `act`, so it runs the `go build` command that `act build` prints. Build in a Go image, then copy the static binary into a minimal one:

```dockerfile title="Dockerfile"
FROM golang:1.27 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /blog .

FROM gcr.io/distroless/static-debian12
COPY --from=build /blog /blog
ENV APP_ENV=production
USER nonroot
EXPOSE 3000
ENTRYPOINT ["/blog"]
```

A `.dockerignore` that leaves out `.git` also leaves out the version: the binary then reports `(devel)`. Copy `.git` into the build stage when you want the version recorded.

See also: [Configuration & Environment](configuration.md), [Commands reference](reference/commands.md#act-build), [Health checks](guides/digging-deeper/health-checks.md)
