Projects and Services

A Project gathers several Services that ship together, such as a frontend, its API and a background worker, so they can share a repository, environment variables and path routes. Each Service keeps its own builds, environments, domains and deployments; the Project only adds a layer of shared configuration on top.

This feature is experimental and currently in nightly builds only; it is not in a beta or stable release. Names, CLI commands and behavior may change before it ships. See Feature maturity.

Terminology on this page. In this feature, a Service is one deployable app, and a Project is the parent that groups Services. Everywhere else in these docs, and in the published CLI, a single deployable app is still called a "project" (see Projects): that is what this page calls a Service. In the CLI and API the parent is called a project group (project-groups), and the projects command still manages Services.


What a Project is

A Service is one deployable application. A Project is a parent above Services:

  • It has a name, a slug, an optional description and a list of Services.
  • A Service belongs to at most one Project. Adding a Service to a Project moves it out of the Project it was in.
  • A Project is optional. A Service that is in no Project behaves exactly as it does today.
Diagram: Project: Shop, Service: web, Service: api, Service: worker, Repository and branch, Shared variables, Path routes: /api on web goes to api

When to use one

Create a Project when Services ship together:

  • A monorepo with several deployable apps (web, API, workers) on one branch.
  • A frontend that calls a same-origin /api served by another Service.
  • Several Services that need the same configuration, such as LOG_LEVEL or an external API key.
  • A multi-service template, such as an app made of a backend, a frontend and a database.

Keep standalone Services when:

  • The app is self-contained and shares nothing with other Services.
  • You are deploying a single container or static site.

Use a separate Service, not an environment, when the code is a different deployable app (different build, different domain, separate analytics). Use environments within one Service for staging and branch previews of the same app.


What a Project shares

A Project can hold three things for its Services. Each one is optional.

  • Name
    Repository
    Description

    One repository and branch for the whole Project, for monorepos. Services that follow the Project's repository build from it, each from its own directory. Changing the Project's repository or branch updates every Service that follows it. A Service can stop following and keep its current settings.

  • Name
    Shared variables
    Description

    Variables every Service receives, for example LOG_LEVEL or API_BASE_URL. A Service's own variable with the same name wins. See Shared variables and precedence.

  • Name
    Path routes
    Description

    Serve a path of one Service's domains from another Service, for example /api on the frontend's domain from the API Service, so the browser sees one origin. See Path routes.

What stays per Service

Grouping Services does not merge them. Each Service keeps its own:

  • builds and deployment history, and whether it deploys on push
  • environments, domains and certificates
  • framework preset, directory, resources, replicas and health check
  • environment variables, which override the Project's shared ones
  • linked databases and storage
  • analytics, error tracking, logs and monitors
  • protections: password, attack mode and limits. A request routed from another Service's domain gets the target Service's protections.

Removing a Service from a Project, or deleting the Project, never stops or deletes a Service. It keeps running as a standalone Service and stops receiving the Project's shared variables on its next deployment. Path routes that involve it are removed.


Shared variables and precedence

A Project's shared variables are one more layer in the environment a deployment receives. For a deployment of a Service, from highest to lowest priority:

  1. Values Temps sets for the platform, such as SENTRY_DSN, TEMPS_API_URL, TEMPS_API_TOKEN and CRON_SECRET. A Project variable cannot replace them. HOST (default 0.0.0.0) is the exception: a Service or Project variable can set it.
  2. The Service's own variables (the project-level and environment-level values from Manage Environment Variables). Where a Service variable and a Project variable with the same name both apply to the environment, the Service's value wins.
  3. The Project's shared variables. Within the Project, a variable scoped to the environment's slug (for example production) wins over the Project's all-environments variable with the same name.
  4. Defaults from linked databases and storage, such as DATABASE_URL from a linked PostgreSQL. A Project variable overrides them, so one shared value can point every Service at the same external database.

Values supplied by an operator-configured secrets manager sit above both Service and Project variables, because they override everything except the platform-owned values in step 1.

Rules that apply to Project variables:

  • Environment scope. A Project variable is either scoped to one environment slug, which applies only to environments with exactly that slug, or applies to all environments.
  • Previews are opt-in. An all-environments Project variable reaches preview environments only when it is marked to include previews, which is off by default. A preview branch therefore never picks up production credentials by accident.
  • Names are case-sensitive. LOG_LEVEL and log_level are two variables.
  • Secrets are write-only. A secret Project variable can be replaced but never read back, and its value never appears in error messages.
  • Changes apply on the next deployment of each Service. Running containers keep the values they started with.
  • A failed decryption fails the deploy. If a Project variable cannot be decrypted, the deployment stops with an error naming the Service, environment, Project and variable key, rather than booting a container without it.

Because the Service's own values win, you can set a default once on the Project and override it for a single Service. See Environment Variables Reference: Project variables for where this layer sits in the full order.


Path routes

A path route sends requests under a prefix, such as /api, on one Service's domains to another Service of the same Project. The frontend calls /api on its own origin and the API Service answers, with no CORS configuration, no build-time API URL and no proxy container.

Both Services must be in the same Project. Each Service keeps its own domains; a route only adds a path to the source Service's domains.

  • Name
    Every domain of the source
    Description

    A route belongs to the source Service, not to one hostname. It applies to the environment's default domain, custom domains and preview domains, including domains you add later. It takes effect without a redeploy.

  • Name
    Environments pair by slug
    Description

    /api on web's production goes to api's production, staging to staging. A preview goes to the target's preview of the same branch. There is no fallback: a preview frontend never calls the production API. When no matching environment exists, requests under the path fail rather than falling back.

  • Name
    Whole segments, longest prefix wins
    Description

    /api matches /api and /api/users, never /apiary. With routes for /api and /api/v2, /api/v2/users goes to the /api/v2 target.

  • Name
    Keep or strip the prefix
    Description

    By default /api/users?page=2 reaches the target unchanged. With strip prefix it arrives as /users?page=2 and carries an X-Forwarded-Prefix: /api header so the target can build links and redirects. A client-supplied X-Forwarded-Prefix on a routed request is always dropped.

  • Name
    Reserved paths
    Description

    Paths the platform answers on every domain cannot be routed: /_temps, /api/_temps, /api/__temps, /api/otel and /.well-known. A route for /api still leaves /api/_temps (analytics ingestion) to Temps. A prefix cannot be /.

  • Name
    The target's protections apply
    Description

    A routed request gets the target Service's password, attack mode and limits, not the source's. If the frontend is password-protected, protect the API too.

  • Name
    Cookies are shared
    Description

    Under one origin, the frontend and the target see the same cookies. That is usually the reason to route, but it means the target also receives the frontend's cookies.

Path routes are designed in upstream ADR-050, which is still marked Proposed.


Monorepo import

When one repository holds several apps (for example apps/web, apps/api and services/worker), you can give the repository to the Project and import each app as a Service:

  • The Project holds the repository and branch, either through a git connection or as a public GitHub or GitLab repository.
  • Temps scans the branch and lists the apps it detects, with their directory and build preset.
  • Each app you pick becomes a Service that follows the Project's repository, building from its own directory.
  • Apps that already have a Service (same repository and directory) are marked as already deployed, so importing twice does not create duplicates.
  • The import runs on the server one step at a time and resumes from the step that failed, so retrying does not create duplicates.

A public repository is read without a git connection, so pushes to it cannot trigger deploys; deploy each Service when you want it updated, or connect the repository through a git connection.

Path-filtered deploys are not implemented. Without them, a push to a shared repository is queued for every Service that follows it. A design for deploying only the Services whose directories changed is written up as ADR-049 (Proposed), but the merged code does not implement it.


Hosting: apps that run elsewhere

The same feature adds a per-Service hosting setting:

  • Hosted: Temps builds and runs the app. This is how every Service works today.
  • External: the app runs somewhere else (Vercel, Netlify, your own VPS). Temps never deploys it; deploy, redeploy, rollback and promote are refused, and pushes to a connected repository do not trigger builds. It still has analytics, session replay, error tracking and OpenTelemetry traces.

An External Service can be in a Project like any other, so a Project can hold both the apps Temps runs and the ones it only observes. Hosting is a setting of each Service, not something a Project shares. Switching a Service to External does not stop deployments that are already running.

An External Service does not receive variables from Temps, because Temps never starts its containers. Configure its SDKs with values you copy: an analytics ingest key, the Service's DSN for error tracking and an OpenTelemetry exporter pointed at Temps (see OpenTelemetry).


Multi-service templates

Templates can describe an app made of several Services in one file. Creating such a template from the CLI or console:

  1. creates a new Project, or uses one you pick,
  2. creates or links each managed database the template lists,
  3. adds the template's shared variables to the Project, generating secrets where the template says so,
  4. creates every Service in the Project, with its own directory, preset, port and resources,
  5. adds the path routes between them, then deploys.

The template schema gains shared_env_vars, a services list with per-service directory, preset, exposed_port, env_vars and database_bindings, and a routes list with source, prefix, target and strip_prefix:

version: "3"
templates:
  - slug: shop
    name: Shop
    version: 1.0.0
    kind: app
    databases:
      - postgres
    shared_env_vars:
      - name: SESSION_SECRET
        secret: true
        default_generator: random_secret
      - name: LOG_LEVEL
        default: info
    services:
      - name: backend
        directory: backend
        preset: dockerfile
        exposed_port: 8080
      - name: frontend
        directory: frontend
        preset: vite
    routes:
      - source: frontend
        prefix: /api
        target: backend
        strip_prefix: false

Key rules the server enforces for multi-service templates:

  • A service built from source must pin a fixed git ref (a tag or commit), because main moves.
  • A service that runs a prebuilt image must reference it by @sha256: digest.
  • Every database in databases must be linked to at least one service.
  • A secret shared variable must use a generator or a value supplied at creation, never a literal default.
  • A route's source and target must be two different services of the template, and its prefix cannot be / or a reserved platform path.

Creation is one server-side operation recorded with an id. If it stops or a step fails, you continue the same operation instead of starting again, and steps that already finished are not repeated.

Unified templates are designed in upstream ADR-051, which is still marked Proposed. The exact template schema and the kind values (the old service kind is folded into app) are not final.


CLI examples

These commands are in upstream main and nightly builds but are not in the published @temps-sdk/cli yet; running them against a release without them fails with an unknown command. In the CLI, a Project is managed with project-groups (alias pg) and a Service with projects. The separate services command manages databases and caches, not these.

# Create a Project with two existing Services (slugs or ids from `projects list`)
bunx @temps-sdk/cli project-groups create --name "Acme Shop" --service web --service api

# Add another Service later, and check the result
bunx @temps-sdk/cli project-groups add acme-shop worker
bunx @temps-sdk/cli project-groups show acme-shop
bunx @temps-sdk/cli projects group -p worker

# Shared variables
bunx @temps-sdk/cli project-groups vars set acme-shop LOG_LEVEL info
bunx @temps-sdk/cli project-groups vars set acme-shop API_BASE_URL https://api.example.com -e production
bunx @temps-sdk/cli project-groups vars set acme-shop PAYMENTS_API_KEY --secret
bunx @temps-sdk/cli project-groups vars list acme-shop
bunx @temps-sdk/cli project-groups vars inherited web

# Path routes: /api on web is served by api
bunx @temps-sdk/cli project-groups routes add acme-shop web /api api
bunx @temps-sdk/cli project-groups routes list acme-shop

# Monorepo: set the repository, preview the import, then import
bunx @temps-sdk/cli project-groups repo set acme-shop --repo acme/shop --branch main --connection 3
bunx @temps-sdk/cli project-groups import acme-shop --dry-run
bunx @temps-sdk/cli project-groups import acme-shop

# Follow or retry a template creation or import
bunx @temps-sdk/cli instantiations list --group acme-shop
bunx @temps-sdk/cli instantiations show 42 --wait
bunx @temps-sdk/cli instantiations retry 42

# Hosting
bunx @temps-sdk/cli projects create --external --name marketing-site
bunx @temps-sdk/cli projects hosting external -p marketing-site

# Create an app from a multi-service template
bunx @temps-sdk/cli templates create shop --project "Shop"

The new commands are summarised in the CLI reference: project-groups, instantiations, projects group, projects hosting and templates create. project-groups, instantiations, templates create and projects hosting print a one-time maturity notice before they run.


Limitations and maturity

  • Nightly builds only. It is merged upstream but not in a beta or stable release yet, so it carries no stability promise. Naming and inheritance rules may still change.
  • Proposed designs. The three design records behind it (ADR-049 monorepo path-filtered deploys, ADR-050 path routes between Services, ADR-051 unified templates) are all marked Proposed. Path-filtered deploys are not implemented.
  • One Project per Service. A Service cannot be in two Projects.
  • No environment merging. Projects share configuration, not environments: path routes pair environments by slug and shared variables resolve per environment.
  • Downgrades. The same change moves service-template snapshots to a new schema version that an older binary cannot read, so downgrading after upgrading to a build that includes it is not supported. Take a backup before trying a nightly build.

Related: Projects, Manage Environment Variables, Environments, Reverse Proxy Routes, Feature Maturity.

Last updated

Was this page helpful?