Restart Policies and Multi-Service Stacks

ជំពូកនេះមិនទាន់ត្រូវបានបកប្រែជាភាសាខ្មែរនៅឡើយទេ — សូមអានជាភាសាអង់គ្លេសខាងក្រោម។

Table of Contents

  1. Restart Condition, in practice
  2. batch-job-none-restart
  3. multi-service-stack
  4. Deploying the two-service stack
  5. Beginner Pitfalls

1. Restart Condition, in practice

A stack's Policy & Resource tab (see Part 1) sets Restart Condition to one of three values:

  • Any — restart no matter how the process exited (success or failure). Right for a long-running server (e.g. docker-hello-world) — if it crashes, or exits for any other reason, Run App brings it back.
  • On failure — restart only on a non-zero exit code. Useful for a job you want retried a few times on transient failure, but not re-run after it succeeds. Combine with Restart Max Attempts so it doesn't retry forever.
  • None — never restart, regardless of exit code. Right for a one-off container that's supposed to run once and stop, like a database migration or import job. Leaving this on Any for a job like that turns it into an infinite loop, since Run App keeps re-running it every time it exits.

2. batch-job-none-restart

A container that does a fixed amount of "work" and then exits — standing in for a one-off job like a migration or data import:

const http = require("http");

const port = process.env.PORT || 3000;
const jobName = process.env.JOB_NAME || "demo-migration";
const workSeconds = Number(process.env.WORK_SECONDS || 10);
const shouldFail = process.env.FAIL === "true";

let status = "running";
const startedAt = Date.now();

const server = http.createServer((req, res) => {
  res.setHeader("Content-Type", "application/json");
  res.end(
    JSON.stringify({
      job: jobName,
      status,
      uptimeSeconds: Math.floor((Date.now() - startedAt) / 1000),
    })
  );
});

server.listen(port, () => {
  console.log(`[${jobName}] listening on port ${port}, working for ${workSeconds}s`);
});

setTimeout(() => {
  if (shouldFail) {
    console.error(`[${jobName}] failed`);
    status = "failed";
    process.exitCode = 1;
    server.close(() => process.exit(1));
    return;
  }

  console.log(`[${jobName}] done`);
  status = "done";
  server.close(() => process.exit(0));
}, workSeconds * 1000);

It still serves one route (GET /, returning { job, status, uptimeSeconds }) while it "works" — Container Port needs something listening so the stack has a status to check via its domain/port before the job exits. After WORK_SECONDS, the process exits 0 normally, or 1 if FAIL=true.

Locally, docker ps -a after running it shows the container as Exited (0) — it doesn't come back on its own. Docker's own --restart flag isn't set here; Run App's Restart Policy is what this example is about, and that only applies once the image is deployed as a Run App stack.

Deploy it, set Restart Condition to None, then CREATE & DEPLOY — watch the service run once and stop in Part 3's service view. Then try redeploying the same stack with Restart Condition set to Any instead, and FAIL=true, to see the crash-loop behavior this option is meant to avoid for a job like this.

3. multi-service-stack

Two small Express services — api (returns a JSON list) and web (fetches from api and renders it as HTML) — deployed as one Run App stack with two services. This is the example for the ADD SERVICE button and the Depend On field from Part 1, rather than the single-service flow docker-hello-world uses.

  • api/ — GET /api/items, GET /health. Reads PORT (default 4000).
  • web/ — GET / (renders items from api), GET /health. Reads PORT (default 3000) and API_URL (default http://localhost:4000), which it uses to reach the api service.

Locally, Docker Compose gives each service a container hostname automatically, so web reaches api at http://api:4000:

docker compose up --build

Visit http://localhost:3000 — it should show three items fetched from the api service.

4. Deploying the two-service stack

  1. Build both images for linux/amd64 and push them to a registry:

    docker login
    
    docker buildx build --platform linux/amd64 \
      -t <your-dockerhub-username>/run-app-multi-service-api:latest \
      --push ./api
    
    docker buildx build --platform linux/amd64 \
      -t <your-dockerhub-username>/run-app-multi-service-web:latest \
      --push ./web
    
  2. Create one stack containing both services:

    • Add the api service first (Service Info → your run-app-multi-service-api image, container port 4000).
    • Click ADD SERVICE and add web (your run-app-multi-service-web image, container port 3000), setting Depend On to the api service.
  3. Once api is deployed, open its Service Info tab (see Part 3) and copy its Service Network value. Set API_URL for the web service to http://<that value>:4000. This value is specific to your own stack (it embeds a random uid), so it can't be hardcoded in this example — copy it from your dashboard after api is deployed.

5. Beginner Pitfalls

  • Leaving Restart Condition on Any for a one-off job. It turns a job that should run once into an infinite crash/restart loop.
  • Hardcoding API_URL between services. The Service Network value embeds a random account-specific uid — it must be copied from your own dashboard after the depended-on service is deployed, not reused from an example.
  • Forgetting to set Depend On. Without it, web may start before api is reachable — though Run App doesn't block startup on it, so a missing dependency shows up as failed requests, not a deploy error.
  • Testing docker-compose.yml's API_URL against a real Run App stack. http://api:4000 only resolves inside Compose's own network — once deployed to Run App, API_URL must use the actual Service Network value instead.

ទិញកាហ្វេជូនខ្ញុំ

ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!