docker build -t jayaraj0781/tasktracker-frontend:latest .
docker push jayaraj0781/tasktracker-frontend:latest
docker pull jayaraj0781/tasktracker-frontend:latest
docker build -t jayaraj0781/tasktracker-backend:latest .
docker push jayaraj0781/tasktracker-backend:latest
docker pull jayaraj0781/tasktracker-backend:latest
helm upgrade --install tasktracker ./tasktracker -n tasktracker
web page is available at: http://task.local/
Vite's import.meta.env.* values are baked into the build and are not available at container runtime. This prevents using a single frontend image across environments with different backend URLs.
Serve a small runtime JS file (config.js) from Nginx that is generated from a template at container start. The React app reads window.BACKEND_API_URL instead of import.meta.env.
- config.template.js (in frontend root)
// config.template.js
window.__BACKEND_API_URL__ = "${BACKEND_API_URL}";- Dockerfile (multi-stage)
# Stage 1: build
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: serve with nginx
FROM nginx:1.28-alpine
# envsubst (gettext) for runtime replacement
RUN apk add --no-cache gettext
COPY --from=build /app/dist /usr/share/nginx/html
COPY config.template.js /usr/share/nginx/html/config.js
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/entrypoint.sh"]- entrypoint.sh
#!/bin/sh
# Replace ${BACKEND_API_URL} with the runtime env value
envsubst '${BACKEND_API_URL}' < /usr/share/nginx/html/config.js > /usr/share/nginx/html/config.tmp.js
mv /usr/share/nginx/html/config.tmp.js /usr/share/nginx/html/config.js
# Start nginx in foreground
nginx -g 'daemon off;'- Load config before app in index.html
<!-- ensure this appears before your main bundle -->
<script src="/config.js"></script>
<script type="module" src="/src/main.jsx"></script>- Read value in React
// example: src/config.js
export const BACKEND_BASE_URL = window.__BACKEND_API_URL__ || "http://localhost:5001";- docker-compose service snippet
frontend:
image: jayaraj0781/tasktracker-frontend:latest
ports:
- "5173:80"
environment:
- BACKEND_API_URL=http://backend:5001
depends_on:
- backend
networks:
- app-network- config.template.js contains a placeholder ${BACKEND_API_URL}.
- Docker entrypoint replaces the placeholder with the actual environment variable.
- Nginx serves config.js as /config.js.
- React app loads /config.js and reads window.BACKEND_API_URL.
- API calls use the correct backend URL at runtime.
The frontend was initially unable to reach the backend because it was trying to call an internal Kubernetes service URL like http://tasktracker-backend:5001/auth/signup or tasktracker.svc.cluster.local.
These internal service names are resolvable only inside the Kubernetes cluster, not from the browser. Since the browser runs outside the cluster (on the user’s machine), it couldn’t resolve that hostname, causing the frontend API calls to fail.
To resolve this, the Ingress was configured to act as a reverse proxy for both the frontend and backend.
The host task.local (exposed via Ingress) now routes:
/auth and /api → backend service (inside cluster)
/ → frontend service
This allows the frontend to make API calls to http://task.local/auth/..., which the Ingress correctly forwards to the backend service.
Originally, the Ingress included the annotation:
nginx.ingress.kubernetes.io/rewrite-target: /
This caused incoming requests like /auth/signup to be rewritten to /signup before reaching the backend. Since the backend expected the full /auth/... path, the routes no longer matched, resulting in 404 or failed API calls.
By removing this annotation, the Ingress now forwards requests exactly as received — ensuring /auth/signup remains intact and correctly reaches the backend.
Version: 0.1.0 OpenAPI: 3.1
This backend provides user authentication and per-user task management. Use the authentication endpoints to create and sign in users. Authenticated users can list, create, update, and delete their own tasks.
Assume the API is served at the server root (e.g., https://api.example.com/). All example paths are relative to the base URL.
- Sign up: POST /auth/signup
- Log in: POST /auth/login — returns credentials (token or session cookie) used to authenticate subsequent requests.
- Log out: POST /auth/logout — invalidates the current session/token.
All task endpoints are protected. Include the returned authentication token (e.g., Bearer token in Authorization header or session cookie) when calling /api/users/{username}/tasks endpoints.
Authentication
-
POST /auth/signup
- Summary: Register a new user.
- Request body (application/json):
- username: string (required)
- email: string (required, email)
- password: string (required)
- Responses:
- 201 Created: User created. Returns a message indicating success.
- 400 Bad Request: Validation error.
- 409 Conflict: Username or email already exists.
-
POST /auth/login
- Summary: Authenticate a user.
- Request body (application/json):
- username: string (required)
- password: string (required)
- Responses:
- 200 OK: Authentication succeeded. Returns a token or sets session cookie and a success message.
- 400 Bad Request: Validation error.
- 401 Unauthorized: Invalid credentials.
-
POST /auth/logout
- Summary: Log out the current user.
- Request body: none
- Responses:
- 200 OK: Successfully logged out (or 204 No Content). Session or token invalidated.
Tasks
-
GET /api/users/{username}/tasks
- Summary: List tasks for the specified user.
- Path parameters:
- username: string (required)
- Query parameters: (optional filtering, sorting or pagination can be added)
- Responses:
- 200 OK: Returns an array of Task objects.
- 401 Unauthorized: Missing/invalid authentication.
- 403 Forbidden: Authenticated user not allowed to access another user's tasks.
-
POST /api/users/{username}/tasks
- Summary: Create a new task for the specified user.
- Path parameters:
- username: string (required)
- Request body (application/json):
- title: string (required)
- description: string | null (optional)
- tags: array of tag values (optional)
- completed: boolean | null (optional, default false)
- Responses:
- 201 Created: Returns the created Task object.
- 400 Bad Request: Validation error.
- 401 / 403: Authentication/authorization errors.
-
PATCH /api/users/{username}/tasks/{task_id}
- Summary: Partially update a task.
- Path parameters:
- username: string (required)
- task_id: integer (required)
- Request body (application/json): Partial Task fields to update (e.g., title, description, tags, completed).
- Responses:
- 200 OK: Returns the updated Task object.
- 400 Bad Request: Validation error.
- 401 / 403: Authentication/authorization errors.
- 404 Not Found: Task not found.
-
DELETE /api/users/{username}/tasks/{task_id}
- Summary: Delete a task.
- Path parameters:
- username: string (required)
- task_id: integer (required)
- Responses:
- 204 No Content: Task deleted successfully.
- 401 / 403: Authentication/authorization errors.
- 404 Not Found: Task not found.
Default / Health
-
GET /
- Summary: Index endpoint — basic welcome/info response.
- Responses: 200 OK
-
GET /health
- Summary: Health check endpoint for monitoring.
- Responses:
- 200 OK: Service healthy (may return a simple JSON status).
-
UserSignup
- username: string
- email: string (email)
- password: string
-
UserLogin
- username: string
- password: string
-
Task
- id: integer
- title: string
- description: string | null
- tags: array of string (allowed values: "personal", "work", "study", "hobby", "other")
- completed: boolean
- created_at: string (date-time)
- updated_at: string (date-time)
-
GenericResponse
- msg: string
- type: string (e.g., "success", "error", "info")
- data: object | array | null (optional payload)
Create user (request) { "username": "jdoe", "email": "[email protected]", "password": "StrongPassword123!" }
Login (response) { "msg": "Login successful", "type": "success" // token or cookie will be provided for subsequent requests }
Create task (request) { "title": "Write documentation", "description": "Draft API docs for the backend", "tags": ["work", "personal"], "completed": false }
Create task (response) { "id": 42, "title": "Write documentation", "description": "Draft API docs for the backend", "tags": ["work","personal"], "completed": false, "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-01T12:00:00Z" }
Error response example { "msg": "Invalid credentials", "type": "error" }
- Use HTTPS for all requests to protect credentials.
- Prefer short-lived access tokens with refresh tokens or secure, httpOnly session cookies.
- Validate and sanitize all user-provided input on the server.
- Implement rate limiting and brute-force protection on authentication endpoints.
- Consider pagination, filtering, and sorting for task listings when scaling.