Skip to content

Services ​

services is an object keyed by service name. The key is the name: what -s takes, what deps refer to, and the directory under serviceRootDir. There is no name field.

ts
export default defineConfig({
  serviceRootDir: 'apps',
  services: {
    api: {
      port: ({ envs }) => Number(envs.API_PORT),
      healthcheck: '/health',
    },
    web: {
      port: 3000,
      deps: [{ service: 'api', wait: true }],
      urls: [''],
    },
    worker: {
      command: ['start', { watches: false }],
      autostart: false,
    },
  },
});

Every option is optional. {} is a valid service: it runs the dev script in <serviceRootDir>/<name>.

OptionDefaultWhat it does
portnoneThe dev port, given to the process as PORT.
healthchecknoneA URL polled to tell when the service is ready.
depsnoneServices that run with this one.
command'dev'The script to run, and whether it watches files.
urlsnoneLinks shown in the footer.
autostarttrueWhether it starts with the session.
selectabletrueWhether the picker lists it.
relativeDir<serviceRootDir>/<name>The service's directory.
tokensnoneValues for this service's callbacks.
logsinheritsTimestamps and log formatting for this service.
colorassignedThe color of its log label.
publicOriginfrom its routeWhere a browser reaches it, set part by part.

port ​

  • Type: number | (ctx) => number | undefined

The port the dev process listens on. stackctl injects it as PORT, uses it for healthcheck and urls paths, and frees it when taking over from an earlier session.

ts
api: { port: 3001 },
web: { port: ({ envs }) => Number(envs.WEB_PORT) }, // from the service's .env files

A callback that returns NaN, the usual sign of a missing variable, is an error naming the service and the env files that were read. Only a PORT in the service's own .env file overrides the injected one.

healthcheck ​

  • Type: string | (ctx) => string | { url, timeout }

A URL stackctl polls while the service runs. Once it answers, the service is ready: its dot turns green, and services that wait for it start.

ts
api: { port: 3001, healthcheck: '/health' },
web: { port: 3000, healthcheck: { url: '/', timeout: 5000 } },
  • A path ('/health') is probed at http://localhost:<port>/health: always the service itself, never the reverse proxy.
  • timeout, in milliseconds, is how long one probe may take. The default is 1500. Raise it for a service slow to answer on a cold start, such as a dev server that compiles on its first request.
  • Probing starts moments after the service does and backs off to one probe every couple of seconds.

A service with no healthcheck counts as ready as soon as it is running.

deps ​

  • Type: Array<string | { service, wait? }> | (ctx) => Array<…>

The services that run alongside this one. Selecting web below also runs api and worker:

ts
web: { deps: [{ service: 'api', wait: true }, 'worker'] },
  • wait: true holds this service until that one's healthcheck passes. The service waited for must have a healthcheck.
  • Names are type-checked against the keys of services.
  • Deps are not transitive. Only the services you select have their deps run. If api needs auth, list auth in web's deps too, or select api.

deps can be a callback over { mode, envs, tokens }, resolved once per session, for a dep that only runs in some modes:

ts
api: { deps: ({ mode }) => (mode === 'test' ? ['gateway'] : ['codegen', 'gateway']) },

command ​

  • Type: string | [string, { watches?: boolean }]
  • Default: ['dev', { watches: true }]

The package.json script or Makefile target to run as the dev process, and whether it watches files and reloads on its own.

ts
worker: { command: ['start', { watches: false }] },

watches doesn't change how the service runs. It records whether a restart is needed after a code edit. See After a code edit.

urls ​

  • Type: Array<string | { label, url } | (ctx) => string> | (ctx) => Array<…>

Links on the footer's links row.

ts
web: {
  port: 3000,
  urls: ['', '/admin', { label: 'storybook', url: 'http://localhost:6006' }],
},

A path is based on the service's public origin, and '' is the origin itself. See Links in the footer.

autostart ​

  • Type: boolean
  • Default: true

Set false to leave a selected service stopped when the session starts. Start it with s in the terminal UI, stackctl service start -s <name>, or the control API. A service that waits for it waits until you do.

selectable ​

  • Type: boolean
  • Default: true

Set false to hide a service from the picker. It still runs as another service's dep, or with -s.

relativeDir ​

  • Type: string
  • Default: <serviceRootDir>/<name>

The service's directory, relative to the workspace. Set it for a service that lives somewhere else. It is required when the config has no serviceRootDir.

tokens ​

  • Type: Record<string, string>

Values for this service's callbacks, merged over the top-level tokens. See Tokens.

logs ​

  • Type: { timestamps?: boolean, formatter?: (line: string) => string }

Per-service log options. See Logs.

color ​

  • Type: string

The color of the service's log label: a name ('magenta'), a hex ('#af87ff'), 'rgb(175,135,255)' or 'ansi256(140)'. By default each service gets a color from a palette.

publicOrigin ​

  • Type: { scheme?, subdomain?, domain?, port? } | (ctx) => …

Overrides parts of the service's public origin, the URL in its PUBLIC_ORIGIN. See publicOrigin.

Service names ​

A name must not be an integer-like string ('2'): JavaScript reorders such keys, which would change the start order. An application service and an infra service can't share a name.

Released under the MIT License.