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

π Hello! I'm Sidharth Chauhan, a passionate DevOps Engineer dedicated to automating and optimizing processes to enhance software development and deployment. With expertise in Docker, Kubernetes, CI/CD, AWS, and more, I thrive in environments that leverage cutting-edge technologies and tools.I love sharing my knowledge and experiences through blogging, and I'm always eager to connect with like-minded professionals. Whether you're looking to collaborate on a project, need help with DevOps, or just want to chat about the latest tech trends, feel free to reach out!π Connect with Me:
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
#. 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
docker compose up --build
#. Backend Dockerfile (Go)
# 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:alpineenvironment, but only ship the binary in a cleanalpine:latest.This reduces the attack surface and ensures faster deployments.
#. Frontend Dockerfile (React + Vite)
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
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:15image 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
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 (
backendresolvesdbinternally).
#. Docker Hub
- Backend:- sidharthchauhan/backend-trackly
docker pull sidharthchauhan/backend-trackly
Frontend:- sidharthchauhan/frontend-trackly
docker pull sidharthchauhan/frontend-trackly
#. Running Trackly Locally
Quick start:-
docker compose up --build
And then access the services:
Frontend β
http://localhost:5173Backend β
http://localhost:8080Database β
localhost:5432
Final Thoughts
With this setup, 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.



