This is the same DevBoard UI as the master branch, but now the data comes
from a real backend instead of fake in-memory data.
Three pieces talk to each other:
browser → frontend (React) → backend (Go API) → database (Postgres)
- frontend — the React app. It also forwards anything starting with
/apito the backend. - backend — a small Go program that reads and writes the database.
- database — Postgres, with some example projects and tasks loaded on first start.
There's no login and no AI here on purpose. The whole point is to see how the pieces connect.
- Docker (with Docker Compose, which comes with Docker Desktop).
- That's it. You do not need Node, Go, or Postgres installed — they all run inside containers.
Run all commands from this folder. We'll start the three pieces one by one, the hard way, so you can see exactly what Docker Compose does for you later.
Containers can only find each other by name if they're on the same network. So first we make one:
docker network create devboard-netThe frontend and backend are our code, so we build an image for each. The database is not our code — it's the official Postgres image — so there's nothing to build for it.
docker build -t devboard-frontend ./frontend
docker build -t devboard-backend ./backendThe first build downloads base images and compiles the code, so it can take a few minutes. Later builds are much faster.
We name it postgres. The backend will look for it by that exact name. The
-v ./init/postgres:... line loads the example data the first time it starts.
docker run -d --name postgres --network devboard-net \
-e POSTGRES_USER=devboard \
-e POSTGRES_PASSWORD=devboard \
-e POSTGRES_DB=devboard \
-v "$PWD/init/postgres":/docker-entrypoint-initdb.d:ro \
-p 5432:5432 \
postgres:16-alpineWe name it backend (the frontend looks for this name). We also tell it how to
reach the database with POSTGRES_URL — notice it uses the name postgres.
docker run -d --name backend --network devboard-net \
-e PORT=8080 \
-e POSTGRES_URL="postgres://devboard:devboard@postgres:5432/devboard?sslmode=disable" \
-p 8081:8080 \
devboard-backendIt serves the app on port 4173 inside the container; we map it to 8080 on your machine.
docker run -d --name frontend --network devboard-net \
-p 8080:4173 \
devboard-frontendOpen http://localhost:8080 in your browser — you should see the DevBoard dashboard with some example tasks. (If the page shows an error for a second on first load, the backend is still starting up — just refresh.)
Then check the wiring from the terminal:
curl http://localhost:8081/health # backend says OK
curl "http://localhost:8080/api/tasks?project_id=1" # app → backend → databasedocker rm -f frontend backend postgres
docker network rm devboard-netThe backend finds the database using the name postgres (see POSTGRES_URL).
The frontend finds the backend using the name backend (see
frontend/vite.config.js). So those container names must match, and they
only work because everything is on the same devboard-net network.
That's a lot of typing, and you have to start them in the right order. This is exactly the problem Docker Compose solves.
Compose does everything from Part 1 — the network, the names, the order, the
environment values — from one file (docker-compose.yml).
First, create your settings file (one time only). Compose reads it to fill in passwords and ports, so the stack won't start without it:
cp .env.example .envThen start everything with one command:
docker compose up --buildThe first build can take a few minutes. When it's done, open http://localhost:8080 in your browser.
Stop it:
docker compose down| Piece | Open in browser / curl | Notes |
|---|---|---|
| Frontend | http://localhost:8080 | the app; forwards /api to the backend |
| Backend | http://localhost:8081/health | the Go API (the app uses it via /api) |
| Postgres | localhost:5432 | user / password: devboard / devboard |
You don't even have to remember the Compose commands. Run make to see what's
available:
make # list all commands
make setup # create your .env file (first time only)
make up # build and start everything
make down # stop everything
make logs # watch the logs
make reset # wipe the database and start fresh
make smoke # quick check that everything worksmake up creates .env for you automatically, so it's the simplest way to start.
makeis optional. It's already available on Linux and macOS (on macOS you may need Xcode Command Line Tools:xcode-select --install). On Windows, either use WSL or just run thedocker composecommands from Part 2 directly.
The GitHub workflows deploy only after CI has succeeded on the advanced
branch. CI also supports a manual dispatch, so the complete remote pipeline can
be started and watched from PowerShell:
.\scripts\run-ci-cd.ps1The script dispatches ci.yml, streams every CI job log, then waits for the
workflow_run trigger in cd.yml and streams its deployment logs. It exits
with an error at the failing stage, so CD is never attempted after a failed CI.
Before the first run, authenticate the GitHub CLI in this repository:
gh auth login -h github.comThe workflow files must be committed and pushed to advanced; GitHub Actions
runs the remote branch, not uncommitted files on the local machine. The CD job
also requires an online self-hosted runner with Docker Compose, plus the
repository DOCKERHUB_USERNAME variable and DOCKERHUB_TOKEN secret.
To allow up to an hour for a queued self-hosted deployment:
.\scripts\run-ci-cd.ps1 -TimeoutSeconds 3600All the changeable values (passwords, ports) live in one file. The first time, copy the example:
cp .env.example .env # or: make setup.env.example is the template kept in git. Your real .env is ignored by git,
so in a real project your secrets never get committed.
The browser calls these as /api/...; the backend serves them at the root.
| Method | Path | What it does |
|---|---|---|
| GET | /projects |
list projects |
| POST | /projects |
create a project |
| GET | /tasks?project_id=N |
list tasks in a project |
| POST | /tasks |
create a task |
| PATCH | /tasks/:id |
update a task (e.g. change status) |
| GET | /search?q=&project_id=N |
search tasks by title |
| GET | /health |
health check |
.
├── docker-compose.yml starts frontend + backend + postgres together
├── Makefile short commands (make up, make down, ...)
├── .env.example template for settings (copy to .env)
├── frontend/ React app (Vite). Serves the UI, forwards /api
├── backend/ Go API (main.go + Dockerfile)
└── init/postgres/ schema + example data, loaded on first start
The repository contains GitHub Actions workflows configured with SonarQube (SAST) and OWASP ZAP (DAST) scanning.
To run your own self-hosted SonarQube server on your AWS EC2 instance:
-
Start the SonarQube Container: Ensure Docker is installed on your EC2 instance, then run:
docker run -itd --name SonarQube-Server -p 9000:9000 sonarqube:community
-
Access the Web Interface:
- Make sure port
9000is open in your AWS EC2 Security Group inbound rules. - Access
http://<YOUR_EC2_PUBLIC_IP>:9000in your browser. - Log in using default credentials: Username:
admin/ Password:admin(you will be prompted to change it)
- Make sure port
To enable SonarQube scanning in your GitHub Actions pipeline:
- Get the Host URL:
- If using a self-hosted instance, your
SONAR_HOST_URLis the URL where SonarQube is hosted (e.g.,http://your-sonarqube-ip:9000). - If using SonarCloud, use
https://sonarcloud.io.
- If using a self-hosted instance, your
- Generate a SonarQube Token:
- In SonarQube: Go to your Profile (User Icon) > My Account > Security.
- Under Generate Tokens, enter a token name, select the User Token type, and click Generate.
- Copy the generated token string.
- Add Secrets to GitHub:
- Go to your GitHub Repository settings.
- Navigate to Settings > Secrets and variables > Actions.
- Add two Repository Secrets:
SONAR_TOKEN: Paste the SonarQube token you copied.SONAR_HOST_URL: Paste your SonarQube server URL.
To allow the CI pipeline to build and push images to Docker Hub:
- Navigate to Settings > Secrets and variables > Actions.
- Under the Variables tab, add:
DOCKERHUB_USERNAME: Your Docker Hub username.
- Under the Secrets tab, add:
DOCKERHUB_TOKEN: A Personal Access Token (PAT) generated from Docker Hub.