Docker Visitor Greeting - Multi-Container Demo
This project demonstrates a complete multi-container Docker Compose application. Users submit their name and email through a React frontend, which triggers a Node.js backend to store the visit in PostgreSQL and send a personalized welcome email via Gmail SMTP
- Frontend: React 18 with Clean Monochrome design
- Backend: Node.js (Express), PostgreSQL, Redis, Nodemailer
- Data: PostgreSQL 16-alpine with persistent volumes
- Cache: Redis 7-alpine (5-minute cooldown per email)
- Email: Gmail SMTP integration with HTML templates
- Orchestration: Docker Compose v3.8 with health checks

Project goals
- Multi-container architecture with service dependencies
- Production-ready patterns including rate limiting
- Modern UI with Clean Monochrome design
- Email integration with Gmail SMTP
- Minimal but complete codebase
Architecture overview
-
Frontend (React)
- Clean Monochrome design
- Form validation with success/error states
- Touch-friendly interface
-
Backend (Node.js + Express)
- RESTful API with
/api/submitendpoint - Input validation
- PostgreSQL integration
- Redis-based anti-spam (5-minute cooldown)
- Gmail SMTP integration
- RESTful API with
-
Data layer
- PostgreSQL 16-alpine with persistent volume
visitstable:id,name,email,created_at- Indexes on
emailandcreated_at
-
Cache layer
- Redis 7-alpine with persistent volume
- Key pattern:
email:{email}with 300-second TTL - Anti-spam protection
-
Container orchestration
- Docker Compose v3.8 with service dependencies
- PostgreSQL health check
- Backend waits for PostgreSQL health
- Single bridge network for service communication
Data model
CREATE TABLE visits (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_email ON visits(email);
CREATE INDEX idx_created_at ON visits(created_at);
API surface
POST /api/submit→{ name, email }→{ success: boolean, message: string }- Validates input (name required, valid email format)
- Checks Redis for recent submission (anti-spam)
- Inserts visit record into PostgreSQL
- Sends welcome email via Gmail SMTP
- Sets Redis cooldown key
Request lifecycle
- User submits form in React frontend
- Frontend validates input and sends POST to backend
- Backend checks Redis for spam protection
- If not spam-blocked: inserts into PostgreSQL
- Sends HTML email via Gmail SMTP
- Sets Redis cooldown key (5 minutes)
- Returns success/error response to frontend
Email system
- SMTP Provider: Gmail with App Password authentication
- Template: Professional HTML with inline CSS
- Content: Personalized welcome message with portfolio link
- Styling: Clean Monochrome design matching frontend
Operational concerns
- Local development:
docker compose up --buildfor full stack - Environment:
.envfile for Gmail credentials (not committed) - Persistence: Named volumes for PostgreSQL and Redis data
- Health checks: PostgreSQL readiness ensures backend startup order
- Port management: Frontend (3001), Backend (8080), PostgreSQL (5432), Redis (6379)
Security and reliability
- Input validation: Server-side validation for all inputs
- Rate limiting: Redis-based anti-spam protection
- Error handling: Graceful degradation if Redis/email fails
- Environment secrets: Gmail credentials via environment variables
- CORS: Configured for frontend-backend communication
Performance notes
- Connection pooling: PostgreSQL connection reuse via
pgPool - Indexed queries: Email and timestamp indexes for fast lookups
- Redis caching: Fast spam check without database hits
- Minimal dependencies: Only essential packages for small images
Trade-offs and decisions
- PostgreSQL over SQLite: Better for multi-container scenarios
- Redis over in-memory: Persistent rate limiting survives restarts
- CSS over styled-components: Simpler deployment without CSS-in-JS
- Inline email templates: No external template engine dependency
- Docker Compose over Kubernetes: Simpler for demo and local development

Running the project
-
Prerequisites
# Enable 2-Step Verification on Google Account # Generate App Password: Security → App passwords → Mail -
Environment setup
cp .env.example .env # Edit .env with your Gmail credentials: # GMAIL_USER=your-email@gmail.com # GMAIL_APP_PASS=your-16-char-app-password -
Docker Compose
docker compose up --build # Frontend: http://localhost:3001 # Backend API: http://localhost:8080 -
Health checks
# Check PostgreSQL docker exec hello-visitor-db pg_isready -U admin -d hello_visitor # Check Redis docker exec hello-visitor-redis redis-cli ping # Test API curl -X POST http://localhost:8080/api/submit \ -H "Content-Type: application/json" \ -d '{"name":"Test","email":"test@example.com"}' -
Cleanup
docker compose down # Remove volumes: docker compose down -v --rmi local
Troubleshooting
- Port conflicts: Change ports in
docker-compose.ymlor free existing ports - Email not sending: Verify
.envGmail credentials and 2FA setup - PostgreSQL not ready: Wait for health check to pass (backend will retry)
- Redis unavailable: Anti-spam skipped, emails still send if DB succeeds
- CORS issues: Frontend calls
http://localhost:8080, backend enables CORS
Notes
This project showcases how modern container orchestration enables rapid development of full-stack applications. The Clean Monochrome design demonstrates that minimal, focused UI can be both beautiful and functional. The email integration shows real-world SMTP usage patterns that scale to production systems
The repository demonstrates production ready patterns in a compact - perfect for understanding Docker Compose, multi-service architectures, and modern web development practices