# Trackly Blog 4: Dockerizing a Full-Stack Go + React Application with PostgreSQL

When I started building Trackly, I wanted more than just another side project. I wanted it to be **production-ready**, portable, and a strong showcase of my ability to work with **Docker, Compose, and containerised workflows**.

In this blog, I’ll share how I used Docker and Docker Hub to run Trackly — a **Go backend, React frontend, and PostgreSQL database** — consistently across local development and production.

This is not a “Hello World” Docker example. It’s a **real, full-stack application** containerised end-to-end.

Trackly :- [https://github.com/sidharth-chauhan/Trackly](https://github.com/sidharth-chauhan/Trackly)

## #. Why Docker for Trackly?

Trackly has three core components:

* **PostgreSQL** → database for persistent storage
    
* **Go backend** → REST API + background monitoring jobs
    
* **React frontend** → Vite-based UI for project dashboards
    

Running these manually means installing dependencies, wiring environment variables, and debugging mismatches. With Docker, I get:

* **Consistency** → identical environment across dev, CI/CD, and prod
    
* **Portability** → push images to Docker Hub, run anywhere
    
* **Isolation** → each service runs in its own container, no conflicts
    
* **Simplicity** → one command to launch the entire stack
    

```bash
docker compose up --build
```

---

## #. `Backend Dockerfile` (Go)

```dockerfile
# Stage 1: Build
FROM golang:1.24.1-alpine AS builder
RUN apk add --no-cache git

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN go build -o main ./cmd/main.go

# Stage 2: Runtime
FROM alpine:latest
RUN apk --no-cache add ca-certificates

WORKDIR /root/
COPY --from=builder /app/main .

CMD ["./main"]
```

* **Multi-stage builds** keep the final image small.
    
* I compile in a `golang:alpine` environment, but only ship the binary in a clean `alpine:latest`.
    
* This reduces the attack surface and ensures faster deployments.
    

---

## #. `Frontend Dockerfile` (React + Vite)

```dockerfile
FROM node:20-alpine

WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install

COPY . .
EXPOSE 5173

CMD ["npm", "run", "dev"]
```

* A lightweight Node base image ensures faster builds.
    
* Keeps the dev experience simple with `npm run dev`.
    
* For production, I can easily switch to `npm run build` + serve with Nginx for optimal performance.
    

---

## #. `PostgreSQL Service`

```dockerfile
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: yourpassword
      POSTGRES_DB: openanalytics
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
```

* I rely on the official `postgres:15` image instead of reinventing the wheel.
    
* Credentials and DB name are set via environment variables.
    
* Data persists across container restarts using **Docker volumes**.
    
* Exposes port `5432`.
    

---

## #. `docker-compose.yaml`

```dockerfile
version: "3.9"

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: yourpassword
      POSTGRES_DB: openanalytics
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data

  backend:
    image: sidharthchauhan/backend-trackly:latest
    environment:
      DB_HOST: db
      DB_PORT: 5432
      DB_USER: postgres
      DB_PASSWORD: yourpassword
      DB_NAME: openanalytics
      JWT_SECRET: supersecretkey
    depends_on:
      - db
    ports:
      - "8080:8080"
    restart: unless-stopped

  frontend:
    image: sidharthchauhan/frontend-trackly:latest
    environment:
      VITE_BACKEND_URL: http://backend:8080
    ports:
      - "5173:5173"
    depends_on:
      - db
      - backend
    restart: unless-stopped

volumes:
  postgres-data:
```

* **Service dependencies** → backend won’t start until Postgres is up.
    
* **Environment variables** → configuration is clean and externalized.
    
* **Volumes** → persistent storage for DB data.
    
* **Restart policies** → containers auto-recover if they crash.
    
* **Networking** → services talk to each other by name (`backend` resolves `db` internally).
    

---

## #. Docker Hub

* Backend:- [**sidharthchauhan/backend-trackly**](https://hub.docker.com/repository/docker/sidharthchauhan/backend-trackly/general)
    

```bash
docker pull sidharthchauhan/backend-trackly
```

Frontend:- [**sidharthchauhan/frontend-trackly**](https://hub.docker.com/repository/docker/sidharthchauhan/frontend-trackly/general)

```bash
docker pull sidharthchauhan/frontend-trackly
```

---

## #. Running Trackly Locally

<mark>Quick start:-</mark>

```bash
docker compose up --build
```

And then access the services:

* Frontend → [`http://localhost:5173`](http://localhost:5173)
    
* Backend → [`http://localhost:8080`](http://localhost:8080)
    
* Database → [`localhost:5432`](http://localhost:5432)
    

---

### Final Thoughts

With this setup, [Trackly](https://github.com/sidharth-chauhan/Trackly) became a **fully containerised application**. I can run the same codebase in development, testing, and production — without worrying about environment drift.

Docker and Docker Compose transformed Trackly from just “a project” into a **cloud-ready, portable application**. Publishing my images to Docker Hub turned it into something that anyone can spin up with a single command.

This is exactly why I believe containerization is a foundational skill for modern developers — and Trackly has been my way of mastering it in practice.
