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.
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¶
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¶
Next: Step 7 — A database