Deploy connected services

A few services that call each other? Deploy them together and Dockhold wires them up by URL for you.

Already have a frontend and an API that talk to each other? You can deploy them as one connected app. Each service builds and runs on its own URL, and Dockhold injects each service's URL into the others as an environment variable. You don't deploy one, copy its URL, paste it into the next, and redeploy. You declare which service needs which, and the wiring happens automatically.

Each service is a separate GitHub repository, and each gets its own URL, its own size, and its own deploys. "Connected" only means they're built together and learn each other's URLs. There's no app-count limit, you just need enough compute in your pool to cover every service.

The shape

Take the common case: a web frontend that calls an api backend, and an api that has to allow the web app through CORS. Each one needs the other's URL, and neither URL exists until the service is deployed. That's exactly what connected services solve.

  • web — a frontend, on its own URL, that reads the API's URL from an API_URL variable.
  • api — a backend, on its own URL, that reads the web app's URL from a WEB_ORIGIN variable (for its CORS allow-list).

Your app must listen on 0.0.0.0 and read its port from the PORT environment variable — never localhost, never a hardcoded port. Dockhold assigns PORT at runtime; an app that ignores it can't receive traffic.

Deploy them together

  1. Go to New App and choose Deploy them together.
  2. Add your first service: a name (web), its GitHub repository URL, and the port it listens on. Add the second the same way (api).
  3. Wire them. On the web service, add an environment variable named API_URL, pick Link a service, and choose api. On the api service, add WEB_ORIGIN and link it to web. Each link becomes the linked service's live URL.
  4. Deploy. Dockhold builds both services at once and connects them as each one comes online.

The one thing your code does

You choose the variable name, and your app reads exactly that name. Nothing magic: if you name the link API_URL, your web app reads API_URL from its environment and calls it. Dockhold fills in the value (the linked service's https://…dockhold.app URL) once that service is live.

// web service — read the URL, never hardcode it
const apiUrl = process.env.API_URL;
const res = await fetch(`${apiUrl}/items`);
# api service — allow the web app through CORS
app.use(cors({ origin: process.env.WEB_ORIGIN }));

Add a database to a service

The service that needs a database turns one on with a checkbox. On that service in the form, tick Add a managed database. Dockhold provisions a database for it and sets a DATABASE_URL variable automatically, so your code just reads it.

// the service that owns the database reads DATABASE_URL
const db = connect(process.env.DATABASE_URL);

Turn it on for the service that actually queries the database, usually your API or a full-stack app. A pure frontend doesn't talk to the database, so it doesn't need one. Each database belongs to the service that enabled it.

What you see while it connects

Builds run in parallel, so the two services don't finish at the same moment. While the api is still building, the web service may not reach it yet, and its API_URL isn't set until the api is live. Once both are built, Dockhold injects the URLs and everything connects, sometimes after one automatic restart.

The connected-app view shows each link as either connected (the variable and the service it points to) or "waiting on" the service it still needs. So a service that hasn't reached its target yet reads as "not connected yet," not "broken." If a build fails, fix it and redeploy that one service. The others keep running, and the wiring finishes when the failed service comes online.

A note on private services

Services in a connected app reach each other over their public URLs, the same way any client would. If you make a service private, callers (including a sibling service) must present an access token on every request, just like an outside caller. Connecting a public frontend to a private backend does not bypass that token.

Limits today

  • Web services. Each service is one that serves traffic on a URL. Background workers with no URL aren't part of a connected app yet.
  • One repository per service. Each service points at a whole GitHub repository. Subfolders of a single monorepo and prebuilt images aren't supported here yet.
  • One database per service. Each service can enable its own managed database (see above). A single database shared by several services isn't supported yet, and connecting your own external database is coming later.

Next