# RAG Template: Minimal Docker Compose Project **Purpose:** Template for creating a web application from scratch, including frontend, backend, PostgreSQL and MinIO with automatic SSL via nginx-proxy. **Created:** 2025-10-26 **Version:** 1.0 --- ## Architecture Overview This template creates a complete web application with the following components: - **Frontend** (React + Vite + nginx) - `.bg502.ru` - **Backend** (Go API) - `api..bg502.ru` - **PostgreSQL** - Database - **MinIO** - Object storage (S3-compatible) - **Automatic SSL** - via nginx-proxy (external) --- ## Quick Start ### Step 1: Create Project Structure ```bash # Create project directory mkdir my-project && cd my-project # Create structure mkdir -p backend/cmd/server frontend database touch docker-compose.yml .env ``` ### Step 2: docker-compose.yml File This is the main orchestration file that defines all services, networks, and volumes for your application. ```yaml services: # Backend API (Go) # Handles API requests, connects to PostgreSQL and MinIO backend: build: context: ./backend dockerfile: Dockerfile environment: - DATABASE_URL=postgres://myapp:password@postgres:5432/myapp_db?sslmode=disable - MINIO_ENDPOINT=minio:9000 - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minio_access} - MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minio_secret} - API_PORT=8080 - CORS_ORIGIN=https://${PROJECT_NAME}.bg502.ru - VIRTUAL_HOST=api.${PROJECT_NAME}.bg502.ru - VIRTUAL_PORT=8080 - LETSENCRYPT_HOST=api.${PROJECT_NAME}.bg502.ru - LETSENCRYPT_EMAIL=admin@${PROJECT_NAME}.bg502.ru depends_on: postgres: condition: service_healthy minio: condition: service_healthy restart: unless-stopped networks: - app-network - proxy healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 deploy: resources: limits: memory: 512M reservations: memory: 256M # Frontend (React + nginx) # Serves static React application built with Vite frontend: build: context: ./frontend dockerfile: Dockerfile target: production args: - VITE_API_URL=https://api.${PROJECT_NAME}.bg502.ru environment: - VIRTUAL_HOST=${PROJECT_NAME}.bg502.ru - VIRTUAL_PORT=80 - LETSENCRYPT_HOST=${PROJECT_NAME}.bg502.ru - LETSENCRYPT_EMAIL=admin@${PROJECT_NAME}.bg502.ru restart: unless-stopped networks: - app-network - proxy # PostgreSQL database # Main application database with automatic schema initialization postgres: image: postgres:16-alpine environment: - POSTGRES_USER=${POSTGRES_USER:-myapp} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} - POSTGRES_DB=${POSTGRES_DB:-myapp_db} volumes: - postgres_data:/var/lib/postgresql/data - ./database/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro ports: - "5432:5432" restart: unless-stopped networks: - app-network healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-myapp} -d ${POSTGRES_DB:-myapp_db}"] interval: 10s timeout: 5s retries: 5 deploy: resources: limits: memory: 1G reservations: memory: 512M # MinIO object storage # S3-compatible storage for files, images, and media minio: image: minio/minio:latest command: server /data --console-address ":9001" environment: - MINIO_ROOT_USER=${MINIO_ACCESS_KEY:-minio_access} - MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY:-minio_secret} volumes: - minio_data:/data ports: - "9000:9000" - "9001:9001" restart: unless-stopped networks: - app-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/ready"] interval: 30s timeout: 20s retries: 3 deploy: resources: limits: memory: 512M reservations: memory: 256M volumes: postgres_data: driver: local minio_data: driver: local networks: app-network: driver: bridge proxy: external: true # Important: proxy network must be created beforehand # docker network create proxy ``` ### Step 3: .env File Environment variables for configuration. Change passwords for production! ```bash # Project name (used in domains) PROJECT_NAME=myproject # Database credentials POSTGRES_USER=myapp POSTGRES_PASSWORD=secure_password_here POSTGRES_DB=myapp_db # MinIO credentials MINIO_ACCESS_KEY=minio_access_key MINIO_SECRET_KEY=secure_minio_password_here ``` --- ## Backend (Go) ### Backend Directory Structure ``` backend/ ├── cmd/ │ └── server/ │ └── main.go ├── Dockerfile ├── go.mod └── go.sum ``` ### backend/Dockerfile Multi-stage Docker build: compiles Go binary and creates minimal production image. ```dockerfile # Build stage FROM golang:1.23-alpine AS builder RUN apk add --no-cache git ca-certificates tzdata WORKDIR /app # Copy modules COPY go.mod go.sum ./ RUN go mod download # Copy source code COPY . . # Build binary RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o api ./cmd/server # Final stage FROM alpine:3.18 RUN apk --no-cache add ca-certificates tzdata wget RUN adduser -D -s /bin/sh appuser WORKDIR /app COPY --from=builder /app/api . RUN chown -R appuser:appuser /app USER appuser EXPOSE 8080 CMD ["./api"] ``` ### backend/cmd/server/main.go Main Go API server with Gin framework, PostgreSQL connection, CORS middleware, and basic REST endpoints. ```go package main import ( "database/sql" "log" "net/http" "os" "github.com/gin-gonic/gin" _ "github.com/lib/pq" ) func main() { // Connect to database dbURL := os.Getenv("DATABASE_URL") db, err := sql.Open("postgres", dbURL) if err != nil { log.Fatal("Failed to connect to database:", err) } defer db.Close() // Check connection if err := db.Ping(); err != nil { log.Fatal("Failed to ping database:", err) } log.Println("Database connected successfully") // Create router r := gin.Default() // CORS middleware r.Use(func(c *gin.Context) { origin := os.Getenv("CORS_ORIGIN") c.Writer.Header().Set("Access-Control-Allow-Origin", origin) c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() }) // Health check endpoint r.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "database": "connected", }) }) // API routes api := r.Group("/api") { api.GET("/items", func(c *gin.Context) { // Example database query var count int err := db.QueryRow("SELECT COUNT(*) FROM items").Scan(&count) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(200, gin.H{ "items": []gin.H{}, "total": count, }) }) api.POST("/items", func(c *gin.Context) { var item struct { Name string `json:"name"` } if err := c.BindJSON(&item); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } c.JSON(201, gin.H{ "message": "Item created", "item": item, }) }) } // Start server port := os.Getenv("API_PORT") if port == "" { port = "8080" } log.Printf("Server starting on port %s", port) if err := r.Run(":" + port); err != nil { log.Fatal("Failed to start server:", err) } } ``` ### backend/go.mod Go module definition with required dependencies. ```go module myproject go 1.23 require ( github.com/gin-gonic/gin v1.9.1 github.com/lib/pq v1.10.9 ) ``` --- ## Frontend (React + Vite) ### Frontend Directory Structure ``` frontend/ ├── src/ │ ├── App.tsx │ ├── main.tsx │ └── api.ts ├── public/ ├── index.html ├── Dockerfile ├── nginx.conf ├── package.json └── vite.config.ts ``` ### frontend/Dockerfile Multi-stage build: builds React app with Vite, then serves with nginx. ```dockerfile # Build stage FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . ARG VITE_API_URL ENV VITE_API_URL=${VITE_API_URL} RUN npm run build # Production stage FROM nginx:1.25-alpine AS production COPY nginx.conf /etc/nginx/nginx.conf RUN rm -f /etc/nginx/conf.d/default.conf COPY --from=builder /app/dist /usr/share/nginx/html RUN chown -R nginx:nginx /usr/share/nginx/html EXPOSE 80 HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://127.0.0.1/health || exit 1 CMD ["nginx", "-g", "daemon off;"] ``` ### frontend/nginx.conf Nginx configuration for serving React SPA with proper routing and static asset caching. ```nginx user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # Health check endpoint location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } # SPA routing location / { try_files $uri $uri/ /index.html; } # Cache static assets location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } } } ``` ### frontend/package.json NPM dependencies and build scripts for React + TypeScript + Vite application. ```json { "name": "myproject-frontend", "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", "typescript": "^5.3.0", "vite": "^5.0.0" } } ``` ### frontend/vite.config.ts Vite build configuration with React plugin. ```typescript import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], server: { port: 5173, host: true, }, build: { outDir: 'dist', sourcemap: false, }, }); ``` ### frontend/src/main.tsx React application entry point. ```tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; ReactDOM.createRoot(document.getElementById('root')!).render( ); ``` ### frontend/src/App.tsx Main React component with API integration example. ```tsx import React, { useState, useEffect } from 'react'; import { fetchItems, createItem } from './api'; function App() { const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [newItemName, setNewItemName] = useState(''); useEffect(() => { loadItems(); }, []); const loadItems = async () => { try { const data = await fetchItems(); setItems(data.items); } catch (error) { console.error('Failed to load items:', error); } finally { setLoading(false); } }; const handleCreate = async (e: React.FormEvent) => { e.preventDefault(); try { await createItem({ name: newItemName }); setNewItemName(''); loadItems(); } catch (error) { console.error('Failed to create item:', error); } }; if (loading) return
Loading...
; return (

My Project

setNewItemName(e.target.value)} placeholder="Item name" />
    {items.map((item, index) => (
  • {item.name}
  • ))}
); } export default App; ``` ### frontend/src/api.ts API client with fetch wrapper functions. ```typescript const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080'; export async function fetchItems() { const response = await fetch(`${API_URL}/api/items`); if (!response.ok) throw new Error('Failed to fetch items'); return response.json(); } export async function createItem(item: { name: string }) { const response = await fetch(`${API_URL}/api/items`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item), }); if (!response.ok) throw new Error('Failed to create item'); return response.json(); } ``` ### frontend/index.html HTML entry point for React application. ```html My Project
``` --- ## Database (PostgreSQL) ### database/schema.sql Database schema with tables, indexes, and sample data. Automatically executed on first container start. ```sql -- Create items table CREATE TABLE IF NOT EXISTS items ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Create index CREATE INDEX IF NOT EXISTS idx_items_created_at ON items(created_at DESC); -- Insert sample data INSERT INTO items (name) VALUES ('Example Item 1'), ('Example Item 2'), ('Example Item 3') ON CONFLICT DO NOTHING; -- Comments COMMENT ON TABLE items IS 'Main table for storing items'; COMMENT ON COLUMN items.name IS 'Item name'; ``` --- ## MinIO Configuration ### Initialize MinIO Bucket After starting containers, create bucket via MinIO web interface or CLI: ```bash # Via web interface open http://localhost:9001 # Or via mc (MinIO Client) docker exec -it mc alias set local http://localhost:9000 minio_access_key minio_secret docker exec -it mc mb local/mybucket docker exec -it mc policy set public local/mybucket ``` ### MinIO Usage Example in Go Example code for integrating MinIO client into your Go application. ```go import ( "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) func initMinIO() (*minio.Client, error) { endpoint := os.Getenv("MINIO_ENDPOINT") accessKey := os.Getenv("MINIO_ACCESS_KEY") secretKey := os.Getenv("MINIO_SECRET_KEY") minioClient, err := minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(accessKey, secretKey, ""), Secure: false, // true for production with SSL }) return minioClient, err } ``` --- ## Deployment ### Prerequisites 1. **nginx-proxy must be running** on host: ```bash # Create proxy network (once) docker network create proxy # Start nginx-proxy (if not already running) docker run -d \ --name nginx-proxy \ --network proxy \ -p 80:80 \ -p 443:443 \ -v /var/run/docker.sock:/tmp/docker.sock:ro \ -v nginx-certs:/etc/nginx/certs \ -v nginx-vhost:/etc/nginx/vhost.d \ -v nginx-html:/usr/share/nginx/html \ nginxproxy/nginx-proxy # Start letsencrypt companion docker run -d \ --name nginx-proxy-letsencrypt \ --network proxy \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v nginx-certs:/etc/nginx/certs \ -v nginx-vhost:/etc/nginx/vhost.d \ -v nginx-html:/usr/share/nginx/html \ --volumes-from nginx-proxy \ nginxproxy/acme-companion ``` 2. **DNS records** must point to your server: - `myproject.bg502.ru` -> Server IP - `api.myproject.bg502.ru` -> Server IP ### Launch Project ```bash # 1. Clone/create project git clone myproject cd myproject # 2. Configure .env file cp .env.example .env nano .env # 3. Build and start containers docker compose up -d --build # 4. Check logs docker compose logs -f # 5. Check status docker compose ps # 6. Check health checks docker compose ps | grep healthy ``` ### Verify Deployment ```bash # Backend health check curl https://api.myproject.bg502.ru/health # Frontend health check curl https://myproject.bg502.ru/health # Backend API curl https://api.myproject.bg502.ru/api/items # PostgreSQL (inside network) docker compose exec postgres psql -U myapp -d myapp_db -c "SELECT COUNT(*) FROM items;" # MinIO open http://localhost:9001 ``` --- ## Management ### Useful Commands ```bash # View logs docker compose logs -f backend docker compose logs -f frontend docker compose logs -f postgres # Restart service docker compose restart backend # Rebuild after changes docker compose build backend docker compose up -d backend # Stop all services docker compose down # Stop with volume removal (CAREFUL!) docker compose down -v # Scaling (not for services with VIRTUAL_HOST) docker compose up -d --scale backend=3 ``` ### Update Code ```bash # 1. Stop containers docker compose down # 2. Update code git pull # 3. Rebuild and start docker compose up -d --build ``` ### Backup ```bash # Backup PostgreSQL docker compose exec postgres pg_dump -U myapp myapp_db > backup.sql # Restore PostgreSQL docker compose exec -T postgres psql -U myapp myapp_db < backup.sql # Backup MinIO bucket docker exec mc mirror local/mybucket /backup/mybucket # Backup volumes docker run --rm -v myproject_postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres_backup.tar.gz /data ``` --- ## Monitoring ### Logs ```bash # All logs docker compose logs -f # Last 100 lines docker compose logs --tail=100 # Filter by service docker compose logs -f backend | grep ERROR ``` ### Metrics ```bash # Resource usage docker stats # Volume sizes docker system df -v # Container info docker compose ps -a ``` --- ## Security ### Security Checklist - [ ] Change all passwords in .env - [ ] Use secrets for production - [ ] Configure firewall (only 80, 443) - [ ] Regularly update images - [ ] Use non-root users in containers - [ ] Configure SSL/TLS (automatic via Let's Encrypt) - [ ] Restrict MinIO console access - [ ] Configure backup strategy ### Production Recommendations Resource limits, health checks, and restart policies for production environments. ```yaml # Add to docker-compose.yml for production services: backend: # Resource limits deploy: resources: limits: cpus: '1.0' memory: 512M reservations: cpus: '0.5' memory: 256M # Auto restart restart: unless-stopped # Healthcheck healthcheck: test: ["CMD", "wget", "--spider", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s ``` --- ## Troubleshooting ### Problem: SSL certificate not issued **Solution:** 1. Check DNS records: `dig myproject.bg502.ru` 2. Check letsencrypt logs: `docker logs nginx-proxy-letsencrypt` 3. Ensure ports 80/443 are open 4. Check VIRTUAL_HOST and LETSENCRYPT_HOST in .env ### Problem: Backend cannot connect to PostgreSQL **Solution:** 1. Check postgres is healthy: `docker compose ps` 2. Check DATABASE_URL in environment 3. Check network: `docker network inspect myproject_app-network` 4. Check postgres logs: `docker compose logs postgres` ### Problem: Frontend shows CORS error **Solution:** 1. Check CORS_ORIGIN in backend environment 2. Ensure correct API URL is used 3. Check backend responds: `curl https://api.myproject.bg502.ru/health` ### Problem: MinIO unavailable **Solution:** 1. Check healthcheck: `docker compose ps minio` 2. Check credentials in .env 3. Check ports: `docker compose port minio 9000` 4. Create bucket via console: http://localhost:9001 --- ## Additional Resources ### Documentation - [Docker Compose](https://docs.docker.com/compose/) - [nginx-proxy](https://github.com/nginx-proxy/nginx-proxy) - [Let's Encrypt Companion](https://github.com/nginx-proxy/acme-companion) - [PostgreSQL](https://www.postgresql.org/docs/) - [MinIO](https://min.io/docs/minio/linux/index.html) - [Go Gin Framework](https://gin-gonic.com/docs/) - [Vite](https://vitejs.dev/) - [React](https://react.dev/) ### Template Usage Examples **For RAG agent queries:** ``` "Create new project based on RAG_TEMPLATE_DOCKER.md named 'blog-platform'" "Use template from RAG_TEMPLATE_DOCKER.md to create API with PostgreSQL and MinIO" "Deploy project following RAG_TEMPLATE_DOCKER.md instructions on domain myblog.bg502.ru" ``` --- ## Checklist for New Project - [ ] Created directory structure - [ ] Copied docker-compose.yml - [ ] Created .env with unique passwords - [ ] Created backend/Dockerfile - [ ] Created backend/cmd/server/main.go - [ ] Created backend/go.mod - [ ] Created frontend/Dockerfile - [ ] Created frontend/nginx.conf - [ ] Created frontend/src files - [ ] Created database/schema.sql - [ ] Verified DNS records - [ ] Created proxy network - [ ] Started nginx-proxy - [ ] Executed docker compose up -d --build - [ ] Checked health checks - [ ] Verified SSL certificate - [ ] Created MinIO bucket - [ ] Made test API requests --- **Template Version:** 1.0 **Last Updated:** 2025-10-26 **Author:** Claude (Archon Agent) **Project:** Eventswipe (9817abbc-e8a2-46c8-828c-e16b84fb3eb8)