# Services

Services define how to run the code and connect it to other parts of the infrastructure, both in production and in local development. All configuration for the app is handled through environment variables, which the code should read from.

## Dynamic services

Run a server process exposed via HTTP. TLS is handled automatically.

```hcl
build "api" {
  base = "go"
  command = "go build -o api"
}

service "api" {
  build = build.api
  command = "./api"

  endpoint {
    public = true
  }

  env = {
    PORT = port
  }
}
```

- `command` - Command to start the server
- `endpoint` - Defines a network endpoint (see Endpoints below)
- `port` - Reference to the auto-assigned port (pass this to your server)

## Endpoints

Endpoints define how a service is accessible over the network.

### Single endpoint (most common)

```hcl
service "api" {
  build = build.api
  command = "./api"

  endpoint {
    public = true
  }

  env = {
    PORT = port
  }
}
```

- `public = true` - Makes the endpoint publicly accessible via HTTPS
- Omitting `public` (or `public = false`) keeps the endpoint internal-only

### Multiple named endpoints

A service can expose multiple endpoints on different ports:

```hcl
service "api" {
  build = build.api
  command = "./api"

  endpoint "main" {
    public = true
  }

  endpoint "admin" {}

  env = {
    MAIN_PORT = endpoint.main.port
    ADMIN_PORT = endpoint.admin.port
  }
}
```

When using multiple endpoints, you must use `endpoint.<name>.port` instead of `port` to reference each endpoint's port.

### Implicit endpoints

If a service uses `port` in its env vars but has no explicit endpoint blocks, an implicit internal endpoint is created:

```hcl
service "worker" {
  build = build.worker
  command = "./worker"

  env = {
    PORT = port  # Creates an implicit internal endpoint
  }
}
```

## Inter-service communication

Services can reference other services' endpoints to communicate with each other:

```hcl
service "api" {
  build = build.api
  command = "./api"

  endpoint {
    public = true
  }

  env = {
    PORT = port
  }
}

service "worker" {
  build = build.worker
  command = "./worker"

  env = {
    API_URL = service.api.url  # localhost:PORT in dev, api:80 in prod
  }
}
```

Available service reference attributes:

- `service.<name>.url` - Full URL without scheme (e.g., `localhost:3000`)
- `service.<name>.host` - Host only (e.g., `localhost`)
- `service.<name>.port` - Port only (e.g., `3000`)

For services with multiple named endpoints, you must specify the endpoint:

```hcl
env = {
  MAIN_URL = service.api.endpoint.main.url
  ADMIN_URL = service.api.endpoint.admin.url
}
```

## Workers

Background processes that don't expose HTTP endpoints.

```hcl
build "worker" {
  base = "node"
  command = "npm run build"
}

service "worker" {
  build = build.worker
  command = "npm run worker"
}
```

## Environment variables

```hcl
service "api" {
  build = build.api
  command = "./api"

  endpoint {
    public = true
  }

  env = {
    PORT = port
    NODE_ENV = "production"
    DATABASE_URL = postgres.main.url
  }
}
```

## Service dev configuration

Override how a service runs in development. If the referenced build has no `dev` block, it is skipped.

```hcl
build "nextjs" {
  base = "node"
  command = "npm run build"
  # No dev block = skipped in development
}

service "web" {
  build = build.nextjs
  command = "npm start"

  endpoint {
    public = true
  }

  env = {
    PORT = port
  }

  dev {
    command = "npm run dev"  # Handles building + serving with hot reload
  }
}
```

## Dev-only services

Services that only run during local development, not deployed to production. Useful for running local versions of external cloud services like Temporal, LocalStack, or mock servers.

A service is dev-only if it has a `dev.command` but no top-level `command`:

```hcl
service "temporal" {
  dev {
    command = "temporal server start-dev"
  }
}

service "worker" {
  build = build.worker
  command = "node worker.js"

  env = {
    TEMPORAL_URL = secret.temporal_url  # Production URL from secret
  }

  dev {
    command = "node --watch worker.js"
    env = {
      TEMPORAL_URL = service.temporal.url  # Override with local URL in dev
    }
  }
}

secret "temporal_url" {}
```

Dev-only services:

- Cannot have a `build` reference (they run arbitrary commands)
- Are automatically excluded from production deployment
- Can define endpoints that other services reference during development

## Deploy hooks

Run commands before or after deploying a service.

**Database migrations should use `pre_deploy` hooks.** This ensures migrations complete before the new code starts, and if they fail, the deployment is automatically aborted.

```hcl
build "api" {
  base = "node"
  command = "npm run build"
}

service "api" {
  build = build.api
  command = "node dist/index.js"

  endpoint {
    public = true
  }

  env = {
    PORT = port
    DATABASE_URL = postgres.main.url
  }

  pre_deploy {
    command = "npm run db:migrate"
  }

  post_deploy {
    command = "npm run cache:warm"
  }
}

postgres "main" {}
```

- `pre_deploy` - Runs before the service is deployed (e.g., database migrations)
- `post_deploy` - Runs after the service is deployed (e.g., cache warming)

Hooks run as Kubernetes Jobs using the same container and environment variables as the service. If a hook fails, the deployment is aborted.

### Database migrations

Always add a `pre_deploy` hook for database migrations. The hook runs **before** new service instances start, ensuring:

- The database schema is updated before new code runs
- If migrations fail, the deployment is aborted (no broken state)
- Migrations have access to `DATABASE_URL` and other service env vars

In development, use `specific exec ...` to run migrations/push database changes whenever the schema is changed. Do so automatically unless the user instructs otherwise. For example: `specific exec api -- npm run db:push`.

## Serving static files

To serve static files, run a web server as a dynamic service. For example, using `npx serve`:

```hcl
build "spa" {
  base = "node"
  command = "npm run build"
}

service "frontend" {
  build = build.spa
  command = "npx serve dist -l $PORT"

  endpoint {
    public = true
  }

  env = {
    PORT = port
  }

  dev {
    command = "npm run dev"  # Use your framework's dev server
  }
}
```

---

Related topics:

- Run `specific docs builds` for build configuration
- Run `specific docs exec` for running one-off commands during development
- Run `specific docs postgres` for database configuration
