> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gocopper.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Background Work

Not everything happens during a request. This guide covers the patterns for running work in the background, from a quick goroutine to a dedicated worker binary.

### Managed Goroutines

You should avoid starting a bare `go func()` in a Copper application. Instead, use `Lifecycle.Go`; Copper recovers panics for you and will automatically wait for the goroutine to finish during graceful shutdown:

```go theme={null}
s.lifecycle.Go(func(ctx context.Context) {
	s.processTelemetry(ctx, batch)
})
```

### Long-Running Workers

For a worker loop, combine `Lifecycle.Go` with `Lifecycle.Shutdown()`, which closes when the app begins shutting down:

```go pkg/telemetry/poller.go theme={null}
func (p *Poller) Start() {
	p.lifecycle.Go(func(ctx context.Context) {
		ticker := time.NewTicker(30 * time.Second)
		defer ticker.Stop()

		for {
			select {
			case <-p.lifecycle.Shutdown():
				return
			case <-ticker.C:
				if err := p.pollRockets(ctx); err != nil {
					p.logger.Error("Failed to poll rocket telemetry", err)
				}
			}
		}
	})
}
```

To learn more about the shutdown mechanics, see [App Lifecycle](/architecture/app-lifecycle).

### A Dedicated Worker Binary

When background work outgrows the web process, you may give it its own binary. Add a `cmd/jobs` directory with the same `main.go`/`wire.go` shape as `cmd/app`, wire the same `app.WireModule`, and start a runner instead of the HTTP server:

```go cmd/jobs/main.go theme={null}
func main() {
	app := copper.New()

	runner, err := InitRunner(app)
	if err != nil {
		app.Logger.Error("Failed to init jobs runner", err)
		os.Exit(1)
	}

	app.Start(runner)
}
```

A runner is anything with a `Run() error` method (the `copper.Runner` interface). Both binaries share one dependency graph, and `copper build` compiles every target under `cmd/`, so deploying the worker is simply deploying another binary from the same codebase.

### Work Tied to a Transaction

Sometimes background work should only happen if the surrounding database transaction commits, such as sending an email or publishing an event. In that case, register the work with `querier.OnCommit` instead of acting immediately; if the transaction rolls back, the callback never runs. To learn more, see [Transactions](/database/transactions).

### Reacting to Events

When one action should trigger several reactions, you may publish an event and let interested packages subscribe. See [Pub/Sub](/digging-deeper/pubsub).

### Scheduled Work

The simplest cron in a Copper app is an HTTP route. Add an `/internal/...` route that performs the work, and have your scheduler (crontab, Cloud Scheduler, Kubernetes CronJob) POST to it:

```go theme={null}
{
	Path:    "/internal/rebalance-fuel",
	Methods: []string{http.MethodPost},
	Handler: ro.HandleRebalanceFuel,
},
```

```
*/15 * * * * curl -X POST http://localhost:5901/internal/rebalance-fuel
```

This approach gives you request logging, metrics, and manual triggering for free, since running the job by hand is just a `curl`. However, you should keep `/internal/` routes unexposed at the network layer, since the application does not authenticate them.
