Ask
154
@negativesplitz ·

every compose deploy drops about 40 seconds of requests, how do people avoid this Zero Downtime

Single VPS, docker compose, caddy in front. Deploy is git pull && docker compose build && docker compose up -d. Compose stops the old api container, starts the new one, and for roughly 30-45 seconds caddy returns 502 while the new one boots.

It's an internal tool so nobody has complained, but I deploy three or four times a day and I'd like to stop flinching. I don't want kubernetes and I don't want to move to a platform. What's the minimum thing that takes me from 40 seconds of 502 to zero?

11 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @coworking_cass · 2mo ago · 3 replies

    Minimum viable is healthcheck plus a second replica plus letting caddy route around the unhealthy one.

    api:
      deploy:
        replicas: 2
      healthcheck:
        test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
        interval: 5s
        timeout: 2s
        retries: 3
        start_period: 20s
    

    Then docker compose up -d --wait, and caddy load-balancing over both with a health check on /healthz. It pulls an unhealthy backend out of rotation instead of 502ing at it.

    That gets you most of the way, but compose will still cycle both replicas at once unless you push them one at a time. For true rolling on plain compose the honest answer is you script it: start new, wait for healthy, drain old, stop old. It's about 30 lines of bash and it works fine.

    The other honest answer is that this is exactly what Kamal does, and Kamal is not kubernetes. Single VPS, compose-shaped config, does the health-check-and-swap for you.

    168
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @negativesplitz · 2mo ago

      --wait on its own took me from 40s to about 12, which is already a different experience. Looking at kamal for the rest.

      44
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @mulch_mule · 2mo ago

      Worth adding that start_period matters a lot here. Without it, healthcheck failures during boot count as retries and the container gets marked unhealthy before it ever finished starting.

      26
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @flux_and_solder · 2mo ago · 2 replies

    Before any of the orchestration: are you building on the box? docker compose build during a deploy on a small VPS means your build is competing for the same two cores as the app that's still serving. Build in CI, push to a registry, and deploy becomes pull && up -d — and the pull happens before anything stops.

    That change alone usually removes most of the window, and it turns rollback into a tag change instead of a rebuild.

    71
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @negativesplitz · 2mo ago

      Yeah, I build on the box. That's clearly the first fix — the build is 90 seconds of it and the container swap is just the tail.

      33
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @muslin_mira · 2mo ago · 2 replies

    Handle SIGTERM. A big chunk of perceived deploy downtime is in-flight requests dying because the process exits instantly on the signal.

    process.on('SIGTERM', () => {
      server.close(() => process.exit(0));
    });
    

    And make sure the signal reaches your process at all. If your Dockerfile ends in CMD npm start, npm is PID 1 and it does not forward signals reliably. Use CMD ["node", "server.js"].

    You'll know it's fixed when docker stop takes 300 ms instead of the full ten second timeout.

    49
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @edge_runtime_bo · 2mo ago

      The npm-as-PID-1 thing is everywhere. It also makes every docker compose down take exactly ten seconds, which people accept as normal for years.

      22
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @rubberduck_rae · 2mo ago

    If any part of those 40 seconds is migrations, split them out. Running migrations on container boot means every replica races to run them and your rollout is gated on the slowest DDL statement. Run them as a one-shot step before the swap, and keep them backwards-compatible so old and new code can both talk to the new schema for a minute. That's the part people skip, and then they discover that zero-downtime deploys require zero-downtime schema changes.

    27
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @oncall_omar · 2mo ago · 2 replies

    deploy at 3am on a cron, nobody notices 40 seconds

    14
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @coworking_cass · 2mo ago

      Works right up until the deploy fails at 3am and it's six hours of downtime instead of 40 seconds. Deploy while you're awake and make the deploy boring.

      12
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @mulch_mule · 2mo ago

    Also just check what caddy does when a backend is down. Default is to 502 immediately. With two upstreams and passive health checks configured, it retries the other one and the user sees nothing. Might be a four-line Caddyfile change for you.

    15
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report