ROADMAP plus granular task files per phase. Phase 1 (12 tasks + 1.13 device authority) covers Codec 8/8E/16 telemetry ingestion; Phase 2 (6 tasks) covers Codec 12/14 outbound commands; Phase 3 enumerates deferred items.
6.5 KiB
Task 1.11 — Dockerfile & Gitea workflow
Phase: 1 — Inbound telemetry
Status: ⬜ Not started
Depends on: 1.8 (so the service actually does something), 1.10 (metrics endpoint for healthcheck)
Wiki refs: docs/wiki/sources/gps-tracking-architecture.md § 7.3 Deployment topology
Goal
Produce a multi-stage Docker image and a Gitea Actions workflow that builds and pushes the image to the project's Gitea Container Registry on every push to main and every tag.
Deliverables
Dockerfile— multi-stage build (deps → build → runtime)..dockerignore— already created in task 1.1; verify it excludes.planning/,test/,dist/(rebuilt in image)..gitea/workflows/build.yml— Gitea Actions workflow.compose.yaml(alongside Dockerfile) — example local stack with Redis forpnpm docker:dev. Useful for local testing of the full pipeline.- Documentation updates in
README.mdcovering: build, run locally, run via compose, CI behavior, image registry path.
Specification
Dockerfile
# syntax=docker/dockerfile:1.7
# ---- deps stage: install with cache-friendly pnpm fetch ----
FROM node:22-alpine AS deps
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest-9 --activate
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm fetch
# ---- build stage: compile TypeScript ----
FROM deps AS build
COPY . .
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile --offline
RUN pnpm build
RUN pnpm prune --prod
# ---- runtime: slim, non-root ----
FROM node:22-alpine AS runtime
WORKDIR /app
RUN addgroup -S app && adduser -S -G app app
COPY --from=build --chown=app:app /app/node_modules ./node_modules
COPY --from=build --chown=app:app /app/dist ./dist
COPY --from=build --chown=app:app /app/package.json ./package.json
USER app
EXPOSE 5027 9090
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:9090/readyz || exit 1
CMD ["node", "dist/main.js"]
Notes:
node:22-alpineis small (~100MB final image). If musl-related issues arise (rare with pure JS), fall back tonode:22-slim.- BuildKit cache mounts (
--mount=type=cache) speed up rebuilds significantly; the Gitea runner must support BuildKit (it does by default with modern docker). pnpm prune --prodstrips dev dependencies before the runtime copy.- Healthcheck hits
/readyzso the container reports unhealthy if Redis is unreachable.
Gitea workflow
.gitea/workflows/build.yml:
name: build
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
container: node:22-alpine
services:
redis:
image: redis:7-alpine
steps:
- uses: actions/checkout@v4
- run: corepack enable && corepack prepare pnpm@latest-9 --activate
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm test --coverage
env:
REDIS_URL: redis://redis:6379
build-and-push:
needs: test
if: gitea.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: git.dev.microservices.al
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: git.dev.microservices.al/trm/tcp-ingestion
tags: |
type=ref,event=branch
type=sha,prefix=,format=short
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=git.dev.microservices.al/trm/tcp-ingestion:buildcache
cache-to: type=registry,ref=git.dev.microservices.al/trm/tcp-ingestion:buildcache,mode=max
Tags produced:
- On push to
main:main,<short-sha>,latest. - On tag
v1.2.3:v1.2.3,1.2.3,1.2,latest(because tag push is on default branch context-dependent — verify with the Gitea Actions semantics in your runner version; adjust if necessary). - On PR: tests run, no push.
GITEA_TOKEN is provided by Gitea Actions automatically (similar to GITHUB_TOKEN in GitHub Actions). It must have package-write scope; configure once in repo settings if the default scope is read-only.
compose.yaml (local dev)
services:
redis:
image: redis:7-alpine
ports: ['6379:6379']
ingestion:
build: .
depends_on: [redis]
ports:
- '5027:5027' # Teltonika TCP
- '9090:9090' # metrics
environment:
NODE_ENV: production
INSTANCE_ID: local-1
REDIS_URL: redis://redis:6379
LOG_LEVEL: debug
restart: unless-stopped
Deployment
Out of scope for this task: how the image is consumed in production (compose pull + restart? K8s? Watchtower?). Recommend a follow-up task once Phase 1 is functional, since the deployment substrate may not be fully decided yet. For now, the image is built and published; humans pull and run it manually.
Acceptance criteria
docker build .succeeds locally and produces an image under 200MB.docker compose upstarts both Redis and the ingestion service; the service's/healthzand/readyzreturn 200.- On push to
main, the Gitea workflow runs tests, builds the image, and publishes it to the registry. The image is visible in the Gitea Packages UI. - On a tag push, the image is also tagged with the version.
- On a PR, only the test job runs (no push).
- BuildKit cache reduces a rebuild-with-no-changes to under 30 seconds.
Risks / open questions
- The exact Gitea Actions feature parity with GitHub Actions varies by runner version. If
docker/metadata-action@v5doesn't work as expected, fall back to a hand-rolled tag generator usinggit rev-parse --short HEAD. GITEA_TOKENpermissions: confirm the default token can push to the registry. If not, switch to a dedicatedsecrets.REGISTRY_TOKEN.- Architecture: build only
linux/amd64for now. Multi-arch (linux/arm64) is a follow-up if anyone needs it for Apple Silicon dev.
Done
(Fill in once complete.)