Welcome to the bongoDev engineering team.
This repository contains a small three-tier incident-management application. The application is functionally complete, but it has not been prepared for container-based deployment. Your responsibility as the DevOps engineer is to design and implement a reliable Docker-based local runtime for the complete system.
You are expected to understand the application, identify its runtime dependencies, create production-conscious container images, connect the services with Docker Compose, preserve database data, and document how another engineer can operate the environment.
Important: This document describes the application and the expected outcome. It intentionally does not provide the Dockerfile or Compose implementation.
Containerize and orchestrate the following services:
- Frontend — React application built with Vite
- Backend — Node.js and Express REST API
- Database — PostgreSQL
After your work is complete, another engineer should be able to clone the repository and start the entire application with one command:
docker compose up --buildThe completed environment must provide:
- A working browser-based frontend
- A healthy backend API
- A PostgreSQL database
- Automatic service-to-service communication
- Persistent database storage
- Environment-based configuration
- Health checks and predictable startup behavior
- Clear operational documentation
The application is an Incident Command Center used to record and manage operational incidents.
Users can:
- View all incidents
- Create an incident
- Assign a severity
- Change an incident's status
- Delete an incident
- View the number of active incidents
The frontend communicates with the backend through HTTP. The backend stores and retrieves incident records from PostgreSQL.
Browser
|
| HTTP
v
Frontend service
|
| /api/*
v
Backend service
|
| PostgreSQL protocol
v
Database service
The browser should access only the frontend for normal application usage. The frontend runtime should route API requests to the backend service.
incident-management-docker-lab/
├── backend/
│ ├── package.json
│ └── src/
│ ├── db.js
│ └── server.js
│
├── frontend/
│ ├── package.json
│ ├── index.html
│ └── src/
│ ├── main.jsx
│ └── styles.css
│
├── .env.example
└── README.md
You will add the required Docker and operational files. At minimum, this will normally include:
backend/Dockerfile
backend/.dockerignore
frontend/Dockerfile
frontend/.dockerignore
compose.yaml
You may add supporting files when justified, such as a web-server configuration, startup script, environment template, or additional documentation.
| Item | Specification |
|---|---|
| Framework | React 18 |
| Build tool | Vite 6 |
| Language | JavaScript with JSX |
| Package manager | npm |
| Development command | npm run dev |
| Production build command | npm run build |
| Build output | dist/ |
| Development port | Vite default, normally 5173 |
| API path used by browser | /api/incidents |
The frontend uses relative API URLs such as:
/api/incidents
/api/incidents/:id/status
The production frontend container must therefore provide a way to forward /api traffic to the backend service, or an equivalent design that does not require hardcoding a host-only backend URL into the browser bundle.
{
"dev": "vite --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview --host 0.0.0.0"
}The final frontend image should:
- Build the React application successfully
- Serve the compiled production assets
- Listen on all required interfaces
- Forward
/apirequests to the backend service - Avoid shipping unnecessary build dependencies in the runtime image
- Return the frontend application for client-side routes where appropriate
- Include a meaningful health check
A multi-stage build is strongly recommended.
| Item | Specification |
|---|---|
| Runtime | Node.js |
| Framework | Express 4 |
| PostgreSQL client | pg |
| Module system | ES modules |
| Package manager | npm |
| Production command | npm start |
| Development command | npm run dev |
| Default application port | 5000 |
| Bind address | 0.0.0.0 |
{
"start": "node src/server.js",
"dev": "node --watch src/server.js"
}During startup, the backend:
- Attempts to connect to PostgreSQL
- Retries failed database connections
- Creates the
incidentstable when it does not exist - Inserts sample records when the table is empty
- Starts the HTTP server on
0.0.0.0
The backend also handles SIGTERM and SIGINT so database connections can be closed during container shutdown.
| Variable | Required purpose | Application default |
|---|---|---|
PORT |
Backend HTTP port | 5000 |
DB_HOST |
PostgreSQL hostname | localhost |
DB_PORT |
PostgreSQL port | 5432 |
DB_NAME |
Database name | incidentdb |
DB_USER |
Database user | incident_user |
DB_PASSWORD |
Database password | incident_password |
NODE_ENV |
Runtime environment | Not enforced by source |
For a containerized environment, DB_HOST must refer to the database service through the Docker network. It must not use the host machine's localhost unless the database is intentionally running in the same container, which is not permitted for this assignment.
The backend image should:
- Use an appropriate supported Node.js base image
- Install only required production dependencies in the final runtime
- Use Docker layer caching effectively
- Exclude local dependencies and unnecessary files from the build context
- Run the application using
npm start - Listen on port
5000inside the container - Run as a non-root user
- Include a backend health check
- Shut down cleanly when Compose stops the service
| Item | Specification |
|---|---|
| Database engine | PostgreSQL |
| Recommended major version | PostgreSQL 16 |
| Internal port | 5432 |
| Default database | incidentdb |
| Default user | incident_user |
Use an official PostgreSQL image rather than creating a custom database image unless you can justify the need.
The database must be configured through environment variables and must use persistent storage.
The backend creates the following logical table:
| Column | Type and behavior |
|---|---|
id |
Auto-incrementing primary key |
title |
Required text, maximum 200 characters |
description |
Text, defaults to empty string |
severity |
low, medium, high, or critical |
status |
open, investigating, or resolved |
created_at |
Timestamp with time zone |
updated_at |
Timestamp with time zone |
The database data must survive container replacement and normal docker compose down operations.
GET /healthHealthy response:
{
"service": "incident-backend",
"status": "healthy",
"database": "connected"
}The endpoint returns HTTP 503 when the backend cannot query the database.
GET /api/incidentsExpected response: HTTP 200 with a JSON array.
POST /api/incidents
Content-Type: application/jsonExample request:
{
"title": "Checkout API unavailable",
"description": "Customers cannot complete payment.",
"severity": "critical",
"status": "open"
}Expected response: HTTP 201 with the created incident.
Validation rules:
titleis requiredtitlemust have at least three charactersseveritymust below,medium,high, orcriticalstatusmust beopen,investigating, orresolved
PATCH /api/incidents/:id/status
Content-Type: application/jsonExample body:
{
"status": "resolved"
}Expected response: HTTP 200 with the updated incident.
DELETE /api/incidents/:idExpected response: HTTP 204.
Your solution must provide exactly three application services:
frontendbackenddatabase
Supporting one-time or administrative containers may be proposed, but you must not replace the three required services.
Create a Compose network that allows:
frontend -> backend
backend -> database
Use Compose service discovery rather than fixed container IP addresses.
Do not use:
- Hardcoded container IP addresses
- Host networking as a shortcut
localhostfor communication between different containers- A single container containing all three application tiers
Only ports needed by the host should be published.
Recommended host access:
| Component | Suggested host port | Required exposure |
|---|---|---|
| Frontend | 3000 |
Yes |
| Backend | 5000 |
Optional but useful for testing |
| PostgreSQL | 5432 |
Not required for normal use |
The database should remain private to the Compose network unless external access is explicitly required for debugging.
Provide a committed .env.example containing non-sensitive example values.
Do not commit a real .env file containing secrets.
The Compose configuration must support at least:
POSTGRES_DB=incidentdb
POSTGRES_USER=incident_user
POSTGRES_PASSWORD=change_me
BACKEND_PORT=5000
FRONTEND_PORT=3000Database credentials must not be embedded in a Dockerfile or application image.
For this exercise, environment variables are acceptable. In a production platform, secrets would normally be supplied through a dedicated secret-management solution.
Use a named volume for PostgreSQL data.
The following workflow must preserve incident records:
docker compose up -d
# Create an incident
docker compose down
docker compose up -d
# The incident must still existThe following command may intentionally delete all data:
docker compose down -vDocument this distinction clearly.
Implement health checks for all services where practical.
Minimum expectations:
- PostgreSQL is healthy only when it can accept connections
- Backend is healthy only when
/healthsucceeds - Frontend is healthy only when its HTTP server responds
The backend must not be treated as ready before PostgreSQL is ready. The frontend must not be treated as ready before the backend is healthy.
Do not rely only on container start order. A started process is not necessarily a ready service.
Your Dockerfiles will be reviewed for:
- Suitable base-image selection
- Deterministic dependency installation where possible
- Efficient Docker layer caching
- Small and appropriate build contexts
- Useful
.dockerignorefiles - Multi-stage builds where beneficial
- Minimal production runtime contents
- Non-root execution where supported
- Clear
CMDorENTRYPOINT - No secrets embedded in image layers
- Reproducible builds
Avoid installing debugging tools or unnecessary packages in production runtime images without justification.
The Compose solution should include sensible restart behavior.
The environment must support:
docker compose up --build
docker compose up -d
docker compose ps
docker compose logs -f
docker compose downLogs must remain visible through standard container output. Do not write application logs only to files inside ephemeral containers.
Submit the following:
backend/Dockerfilebackend/.dockerignorefrontend/Dockerfilefrontend/.dockerignorecompose.yaml- Any required frontend web-server or proxy configuration
.env.example- Updated operational documentation
- A short design note named
DEVOPS_NOTES.md
- Your image and base-image choices
- How frontend API proxying works
- How services discover one another
- How startup readiness is handled
- How PostgreSQL data is persisted
- How secrets would be handled in a real production environment
- Any security improvements you implemented
- Any limitations or future improvements
The assignment is complete only when all of the following pass.
-
docker compose configcompletes successfully -
docker compose buildcompletes successfully -
docker compose up -dstarts all required services -
docker compose psshows the services running - All configured health checks become healthy
- The frontend opens in a browser
- The incident list loads successfully
- A new incident can be created
- Incident status can be updated
- An incident can be deleted
- Backend
/healthreturns HTTP200 - Backend health confirms database connectivity
- The frontend reaches the backend using container networking
- The backend reaches PostgreSQL using container networking
- No fixed container IP address is used
- Cross-container communication does not use
localhost
- PostgreSQL uses a named volume
- Data survives
docker compose downand restart -
docker compose down -vremoves the persistent database data
-
.dockerignoreexists for each custom build context - Database credentials are not embedded in Dockerfiles
- The backend runtime does not run as root
- The frontend uses a production build
- The final frontend runtime does not require the Vite development server
- Unnecessary files are excluded from final images
- Setup instructions work on a clean machine with Docker installed
- Required environment variables are documented
- Start, stop, logs, rebuild, reset, and troubleshooting commands are documented
-
DEVOPS_NOTES.mdexplains the design decisions
Run these commands before submitting your solution.
docker compose configdocker compose builddocker compose up -ddocker compose psdocker compose logs --tail=100 backend
docker compose logs --tail=100 frontend
docker compose logs --tail=100 databasecurl --fail http://localhost:5000/healthcurl --fail http://localhost:5000/api/incidentscurl --fail -X POST http://localhost:5000/api/incidents \
-H "Content-Type: application/json" \
-d '{
"title": "Containerization validation incident",
"description": "Created while validating the Docker environment.",
"severity": "medium"
}'Open:
http://localhost:3000
docker compose down
docker compose up -d
curl --fail http://localhost:5000/api/incidentsConfirm that the validation incident still exists.
This section helps you understand the application before containerizing it. Docker remains the required final delivery.
- A supported Node.js release
- npm
- PostgreSQL running locally
Create a PostgreSQL database matching the configured values, or provide your own values through environment variables.
Example defaults:
Database: incidentdb
User: incident_user
Port: 5432
Host: localhost
cd backend
npm install
export PORT=5000
export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=incidentdb
export DB_USER=incident_user
export DB_PASSWORD=incident_password
npm startWindows PowerShell example:
cd backend
npm install
$env:PORT="5000"
$env:DB_HOST="localhost"
$env:DB_PORT="5432"
$env:DB_NAME="incidentdb"
$env:DB_USER="incident_user"
$env:DB_PASSWORD="incident_password"
npm startIn a second terminal:
cd frontend
npm install
npm run devThe source frontend uses relative /api paths. When running frontend and backend separately without a reverse proxy, you may need a temporary local proxy configuration for development. Do not solve the production container requirement by hardcoding localhost:5000 into the frontend source.
A DevOps engineer is expected to diagnose issues systematically rather than changing files randomly.
-
Validate the Compose model:
docker compose config
-
Check container state:
docker compose ps
-
Read service logs:
docker compose logs backend docker compose logs database docker compose logs frontend
-
Inspect environment variables inside a service:
docker compose exec backend env -
Verify DNS and service connectivity from inside the relevant container.
-
Verify that the process listens on
0.0.0.0, not only127.0.0.1. -
Confirm the internal container port and published host port are not being confused.
-
Confirm PostgreSQL data permissions and the volume mount path.
- Using
localhostto reach another container - Assuming
EXPOSEpublishes a host port - Using
depends_onwithout checking readiness - Storing database files only in the writable container layer
- Running a development server as the production frontend
- Copying
node_modulesfrom the host into an image - Reinstalling dependencies on every source-code change because of poor layer ordering
- Baking passwords into images
- Publishing PostgreSQL unnecessarily
- Treating a running container as a healthy application
| Area | Weight |
|---|---|
| Functional Dockerfiles | 20% |
| Compose orchestration | 20% |
| Networking and configuration | 15% |
| Health checks and startup reliability | 15% |
| Persistence | 10% |
| Security and image optimization | 10% |
| Documentation and explanation | 10% |
Additional credit may be awarded for well-justified improvements such as:
- Docker BuildKit cache optimization
- Read-only runtime filesystem where practical
- Explicit resource constraints for local testing
- Compose profiles for optional developer tooling
- Automated smoke-test script
- Image vulnerability scanning documentation
- CI workflow that builds and validates the images
- Software bill of materials generation
Bonus features must not make the base solution unnecessarily complicated.
Your work is considered done when a new engineer can perform the following on a clean machine:
git clone <repository-url>
cd incident-management-docker-lab
cp .env.example .env
docker compose up --build -dYou must then be able to:
- Open the frontend
- Create and manage incidents
- Confirm backend and database health
- Review logs
- Restart the environment without losing data
- Remove the environment and its data intentionally
- Understand the architecture and operational commands from your documentation
The goal is not merely to make the containers start. The goal is to deliver a containerized application that is understandable, repeatable, secure by default for a training environment, and straightforward to operate.