Add Phase 1 and Phase 2 planning documents
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.
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
# 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 for `pnpm docker:dev`. Useful for local testing of the full pipeline.
|
||||
- Documentation updates in `README.md` covering: build, run locally, run via compose, CI behavior, image registry path.
|
||||
|
||||
## Specification
|
||||
|
||||
### Dockerfile
|
||||
|
||||
```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-alpine` is small (~100MB final image). If musl-related issues arise (rare with pure JS), fall back to `node: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 --prod` strips dev dependencies before the runtime copy.
|
||||
- Healthcheck hits `/readyz` so the container reports unhealthy if Redis is unreachable.
|
||||
|
||||
### Gitea workflow
|
||||
|
||||
`.gitea/workflows/build.yml`:
|
||||
|
||||
```yaml
|
||||
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)
|
||||
|
||||
```yaml
|
||||
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 up` starts both Redis and the ingestion service; the service's `/healthz` and `/readyz` return 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@v5` doesn't work as expected, fall back to a hand-rolled tag generator using `git rev-parse --short HEAD`.
|
||||
- `GITEA_TOKEN` permissions: confirm the default token can push to the registry. If not, switch to a dedicated `secrets.REGISTRY_TOKEN`.
|
||||
- Architecture: build only `linux/amd64` for now. Multi-arch (`linux/arm64`) is a follow-up if anyone needs it for Apple Silicon dev.
|
||||
|
||||
## Done
|
||||
|
||||
(Fill in once complete.)
|
||||
Reference in New Issue
Block a user