Skip to content

Step 6 — One container (and your first failed deploy)

About 25 minutes. Branch: step-6 (and step-6-broken).

The one new idea: the platform's contract with your container is stricter than you think.

This step breaks on purpose

The deploy will fail. Your site keeps serving throughout, which is precisely why the check that fails it exists.

Why anything changes

Tally's roadmap is in the bundle. Changing a date means editing the repository and waiting for a rebuild. That is fine for five items and absurd for a real board. It needs an API — and an API is a program, which means a container.

The image

backend/Dockerfile builds in two stages: Node compiles the React bundle, Python serves it. Node does not ship in the final image.

FROM node:22-alpine AS frontend
WORKDIR /build
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci --no-audit --no-fund
COPY frontend/ ./
RUN npm run build

FROM python:3.12-slim
WORKDIR /srv
COPY backend/requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY backend/app ./app
COPY --from=frontend /build/dist ./static
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

The config, and the deliberate mistake

containers:
  - name: tally
    dockerfile: backend/Dockerfile
    context: .

deploy:
  - name: web
    primary: true
    container: tally
    visibility: public
    port: 8080          # ← wrong on purpose
    health_check:
      path: /api/health
      startup_timeout: 120
    resources:
      memory: 256m

Two lists, and the split matters: containers: is how to build, deploy: is how to run. They are separate because one image can back several services, and at step 9 you use exactly that.

Deploy it.

Watch it fail

The build succeeds — the code is fine. The deploy fails, a couple of minutes later, after startup_timeout expires.

mecca queue diagnose <action-id>
mecca logs tally --type deploy -b tutorial

The container started and answered nothing, because the platform connected to port 8080 and uvicorn is listening on 8000.

port: is not a port you choose. It is a fact you report. It must be the port the process inside the container actually binds — the one in the Dockerfile's CMD.

Meanwhile your site is still up, serving step 5's version. The platform never took the old one down, because the new one never proved itself.

Fix it

Change 8080 to 8000. Deploy. It passes.

The other half: startup_timeout

    health_check:
      path: /api/health
      startup_timeout: 120

120 seconds is the default. A container that is slow to start — a JVM, a large migration, a cold dependency — fails here with an error that reads as "my app is broken" when the truth is "my app had not finished starting". If yours is genuinely slow, raise this rather than hunting for a fault that is not there.

A caveat specific to this app, worth knowing generally

Tally serves the React bundle with a catch-all that answers every unmatched path with index.html and a 200. So a health_check.path pointing at a route that does not exist would still pass — answered by the catch-all.

That is why this step's mistake is the port and not the path. If your app has a catch-all, your health check cannot tell you the path is wrong.

If it didn't work

Symptom Try Usually
Deploy fails after ~2 min mecca logs tally --type deploy Port mismatch, or nothing listening on 0.0.0.0
Container starts then exits mecca logs tally --type container The process crashed — the log has it
Build cannot find the Dockerfile context: is the repo root here; both paths are relative to it
Deploy is killed for memory raise resources.memory 256m is enough for this, not for everything

Reset to the reference

git fetch upstream && git reset --hard upstream/step-6 && git push --force origin tutorial

Next: Step 7 — A database