Full-Stack Setup Troubleshooting Guide
Full-Stack Setup Troubleshooting Guide
Last Updated: February 28, 2026 Covers: React Client + Rails API + Python SaaS templates Tested With: sscli 2.6.13+
Table of Contents
- Overview
- Quick Diagnostics
- Common Issues by Category
- Full-Stack Integration Issues
- Template-Specific Issues
- Best Practices
- Real Error Messages & Solutions
Overview
This guide documents real issues encountered during full-stack setup and integration testing. It covers the most common pitfalls when combining React, Rails, and Python templates in a Docker environment.
What This Guide Covers
- β Frontend-backend connectivity
- β CORS configuration
- β Authentication flows (JWT, Session)
- β Database setup and migrations
- β Docker networking and volumes
- β Environment variable management
- β Port conflicts and permissions
Prerequisites
Before troubleshooting:
# Verify Docker is runningdocker --version && docker-compose --version
# Check available memory (should be 4GB+)docker system info | grep "Total Memory"
# Verify sscli installationsscli --versionQuick Diagnostics
Run these commands to quickly identify common issues:
# 1. Check all service healthdocker-compose ps
# 2. View logs for all servicesdocker-compose logs --tail=50
# 3. Check specific servicedocker-compose logs -f [service-name]
# 4. Verify network connectivitydocker-compose exec client curl http://api:3000/healthdocker-compose exec api curl http://db:5432
# 5. Check environment variablesdocker-compose exec api env | grep -i databasedocker-compose exec client env | grep -i vite
# 6. Test database connectiondocker-compose exec db psql -U postgres -c "SELECT 1;"Common Issues by Category
π΄ BLOCKERS (Cannot Complete Setup)
[B-001] Database Does Not Exist on First Run
Symptoms:
FATAL: database "rails_api_development" does not existPG::ConnectionBad: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failedRoot Cause: Rails/Python services start before the database container is fully initialized and ready to accept connections.
Solution 1: Add health checks to docker-compose.yml
services: db: image: postgres:15 healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5
api: depends_on: db: condition: service_healthySolution 2: Add database creation to entrypoint
Create docker-entrypoint.sh in Rails template:
#!/bin/bashset -e
# Wait for postgresuntil PGPASSWORD=$POSTGRES_PASSWORD psql -h "$DATABASE_HOST" -U "$POSTGRES_USER" -c '\q'; do echo "Postgres is unavailable - sleeping" sleep 1done
echo "Postgres is up - creating database"bundle exec rails db:create db:migrate
exec "$@"Quick Fix:
# Manually create and migrate databasedocker-compose exec api bashbundle exec rails db:create db:migrateexitPriority: P0 (Must fix)
[B-002] Docker Volume Permission Errors (Linux)
Symptoms:
Error: EACCES: permission denied, mkdir '/app/node_modules'SQLite3::CantOpenException: unable to open database fileRoot Cause: On Linux, Docker containers run as root by default, but mounted volumes retain host user permissions, causing conflicts.
Solution 1: Add user mapping to docker-compose.yml
services: client: user: "${UID}:${GID}" environment: - HOME=/tmpSolution 2: Fix permissions in Dockerfile
RUN mkdir -p /app/node_modules && \ chown -R node:node /app
USER nodeSolution 3: Use named volumes instead of bind mounts
Under the service definition:
volumes: - node_modules:/app/node_modulesAt the top-level of docker-compose.yml:
volumes: node_modules:Quick Fix:
# On Linux host, set environment variablesexport UID=$(id -u)export GID=$(id -g)docker-compose upPriority: P0 (Blocker on Linux)
π MAJOR (Significant Workaround Required)
[M-001] CORS Not Configured by Default
Symptoms:
Access to fetch at 'http://localhost:3000/api/v1/users' from origin 'http://localhost:5173'has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on therequested resource.Root Cause: Rails API template does not include CORS configuration by default. React client cannot make API calls.
Solution for Rails:
- Add to
Gemfile:
gem 'rack-cors'- Create
config/initializers/cors.rb:
Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins ENV.fetch('CORS_ORIGINS', 'http://localhost:5173').split(',')
resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head], credentials: true, max_age: 86400 endend- Add to
.env:
CORS_ORIGINS=http://localhost:5173,http://localhost:3001- Rebuild and restart:
docker-compose build apidocker-compose restart apiSolution for Python FastAPI:
In src/infrastructure/api/app.py:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware( CORSMiddleware, allow_origins=os.getenv("CORS_ORIGINS", "http://localhost:5173").split(","), allow_credentials=True, allow_methods=["*"], allow_headers=["*"],)Testing:
# From browser consolefetch('http://localhost:3000/api/v1/health') .then(r => r.json()) .then(console.log)Priority: P1 (Critical for frontend-backend integration)
[M-002] JWT Secret Not Configurable via Environment
Symptoms:
JWT::DecodeError: Signature verification failedNoMethodError: undefined method `[]' for nil:NilClass (accessing ENV['JWT_SECRET'])Root Cause: JWT authentication hardcodes the secret or doesnβt read from environment variables.
Solution for Rails (Devise + JWT):
- In
config/initializers/devise.rb:
config.jwt do |jwt| jwt.secret = ENV.fetch('JWT_SECRET') { Rails.application.credentials.secret_key_base } jwt.dispatch_requests = [ ['POST', %r{^/api/v1/auth/login$}] ] jwt.revocation_requests = [ ['DELETE', %r{^/api/v1/auth/logout$}] ] jwt.expiration_time = 24.hours.to_iend- Generate secret:
openssl rand -hex 32- Add to
.env:
JWT_SECRET=your_64_character_hex_string_hereSolution for Python:
import osfrom datetime import datetime, timedeltafrom jose import jwt, JWTError
SECRET_KEY = os.getenv("JWT_SECRET")if not SECRET_KEY: raise ValueError("JWT_SECRET environment variable not set")
ALGORITHM = "HS256"ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 24 hours
def create_access_token(data: dict) -> str: to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire}) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)Priority: P1 (Security risk)
[M-003] Missing .env.example Files
Symptoms:
Configuration Error: Required environment variable 'DATABASE_URL' not foundUser confusion: "What environment variables do I need?"Root Cause:
Templates donβt include .env.example files, forcing users to guess required configuration.
Solution:
Create .env.example in each template root:
Rails API:
# .env.example for rails-api
# Server ConfigurationRAILS_ENV=developmentPORT=3000
# DatabaseDATABASE_URL=postgresql://postgres:postgres@localhost:5432/rails_api_development
# Redis (optional)REDIS_URL=redis://localhost:6379/1
# AuthenticationSECRET_KEY_BASE=generate_with_rails_secretJWT_SECRET=generate_with_openssl_rand_hex_32
# CORSCORS_ORIGINS=http://localhost:5173,http://localhost:3001
# External Services (optional)STRIPE_API_KEY=sk_test_...SENDGRID_API_KEY=SG...React Client:
# .env.example for react-client
# API ConfigurationVITE_BACKEND_URL=http://localhost:3000VITE_API_ROOT=/api/v1
# AuthenticationVITE_AUTH_TYPE=token
# Feature FlagsVITE_ENABLE_ANALYTICS=falseVITE_ENABLE_DEBUG=true
# External Services (optional)VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...VITE_SENTRY_DSN=https://...@sentry.io/...Python SaaS:
# .env.example for python-saas
# ApplicationAPP_ENV=developmentAPP_PORT=8000LOG_LEVEL=INFO
# DatabaseDATABASE_URL=sqlite:///./dev.db# DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
# SecuritySECRET_KEY=change_this_to_a_secure_random_string
# External APIs (optional)OPENAI_API_KEY=sk-...STRIPE_API_KEY=sk_test_...Usage:
# User copies and configurescp .env.example .env# Edit .env with real valuesPriority: P1 (Quality of life)
[M-004] Token Refresh Not Implemented
Symptoms:
User gets logged out after token expires (24 hours)API returns 401 Unauthorized on all requestsReact app doesn't attempt to refresh tokenRoot Cause: Frontend doesnβt implement token refresh logic; requires manual re-login.
Solution:
Implement token refresh interceptor in React:
import axios from "axios";
const api = axios.create({ baseURL: import.meta.env.VITE_BACKEND_URL,});
// Request interceptor to add tokenapi.interceptors.request.use((config) => { const token = localStorage.getItem("accessToken"); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config;});
// Response interceptor to handle token refreshapi.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config;
// If 401 and we haven't tried refreshing yet if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true;
try { const refreshToken = localStorage.getItem("refreshToken"); const response = await axios.post("/api/v1/auth/refresh", { refresh_token: refreshToken, });
const { access_token } = response.data; localStorage.setItem("accessToken", access_token);
// Retry original request with new token originalRequest.headers.Authorization = `Bearer ${access_token}`; return api(originalRequest); } catch (refreshError) { // Refresh failed, log user out localStorage.removeItem("accessToken"); localStorage.removeItem("refreshToken"); window.location.href = "/login"; return Promise.reject(refreshError); } }
return Promise.reject(error); });
export default api;Backend (Rails) refresh endpoint:
class Api::V1::AuthController < ApplicationController skip_before_action :authenticate_user!, only: [:login, :refresh]
def refresh token = request.headers['Authorization']&.split(' ')&.last payload = JWT.decode(token, nil, false).first rescue nil
if payload && payload['refresh'] user = User.find(payload['user_id']) new_token = generate_access_token(user) render json: { access_token: new_token } else render json: { error: 'Invalid refresh token' }, status: :unauthorized end endendPriority: P1 (User experience)
π‘ MINOR (Small Inconvenience)
[Mi-001] Flash of Protected Content Before Redirect
Symptoms: User briefly sees protected page content before being redirected to login.
Root Cause: Auth check happens after component renders.
Solution:
import { Navigate } from "react-router-dom";import { useAuth } from "../hooks/useAuth";
export function ProtectedRoute({ children }: { children: React.ReactNode }) { const { user, loading } = useAuth();
// Show loading state while checking auth if (loading) { return <div>Loading...</div>; }
// Redirect unauthenticated users if (!user) { return <Navigate to="/login" replace />; }
return <>{children}</>;}Priority: P2 (Polish)
[Mi-002] No Health Check Endpoint
Symptoms: No easy way to verify API is running from browser.
Solution for Rails:
get '/health', to: proc { [200, {}, ['OK']] }
# Or with more detail:class HealthController < ApplicationController skip_before_action :authenticate_user!
def show render json: { status: 'ok', timestamp: Time.current.iso8601, database: database_status, redis: redis_status } end
private
def database_status ActiveRecord::Base.connection.execute('SELECT 1') 'connected' rescue 'disconnected' end
def redis_status $redis.ping == 'PONG' ? 'connected' : 'disconnected' rescue 'disconnected' endendPriority: P2 (Nice to have)
Full-Stack Integration Issues
Docker Compose Networking
Issue: Services Canβt Communicate
Error:
Error: getaddrinfo ENOTFOUND apicurl: (6) Could not resolve host: dbSolution:
Ensure all services are in the same network:
version: "3.8"
services: db: image: postgres:15 networks: - app-network
redis: image: redis:7-alpine networks: - app-network
api: build: ./templates/rails-api networks: - app-network depends_on: - db - redis
client: build: ./templates/react-client networks: - app-network depends_on: - api
networks: app-network: driver: bridgeContainer-to-container URLs:
- Use service names as hostnames:
http://api:3000,postgresql://db:5432 - NOT:
http://localhost:3000(localhost inside container refers to itself)
Environment Variable Propagation
Issue: .env Variables Not Available in Container
Error:
ENV['DATABASE_URL'] returns nil inside containerSolutions:
Option 1: env_file in docker-compose.yml
services: api: build: ./rails-api env_file: - ./rails-api/.envOption 2: Explicit environment mapping
services: api: build: ./rails-api environment: - DATABASE_URL=${DATABASE_URL} - REDIS_URL=${REDIS_URL} - SECRET_KEY_BASE=${SECRET_KEY_BASE}Option 3: Pass through .env file
# In .env file at docker-compose levelDATABASE_URL=postgresql://postgres:postgres@db:5432/app_dev
# docker-compose.ymlservices: api: environment: DATABASE_URL: ${DATABASE_URL}Debugging:
# Check what environment variables are set inside containerdocker-compose exec api env | sort
# Check specific variabledocker-compose exec api bash -c 'echo $DATABASE_URL'Port Mapping vs Internal Ports
Common Confusion
External (host) ports vs Internal (container) ports:
services: api: ports: - "3000:3000" # HOST:CONTAINERContainer-to-container communication:
- β
Use internal port:
http://api:3000 - β Donβt use host port:
http://localhost:3000
Browser-to-container communication:
- β
Use host port:
http://localhost:3000
React .env configuration:
# For development (browser-to-API)VITE_BACKEND_URL=http://localhost:3000
# For SSR or container-to-containerVITE_BACKEND_URL=http://api:3000Template-Specific Issues
Rails API
Issue: Existing Database Schema Conflicts
Error:
ActiveRecord::StatementInvalid: PG::DuplicateTable: ERROR: relation "users" already existsSolution:
# Reset database completelydocker-compose exec api rails db:drop db:create db:migrate
# Or load schema directlydocker-compose exec api rails db:schema:loadIssue: Missing Master Key
Error:
ActiveSupport::MessageEncryptor::InvalidMessageRails credentials are not configuredSolution:
# Generate new credentialsdocker-compose exec api bashEDITOR=vim rails credentials:edit# Save and exit
# Or use environment variables instead of credentialsexport SECRET_KEY_BASE=$(rails secret)React Client
Issue: Vite Dev Server Not Accessible from Host
Error:
Cannot access http://localhost:5173curl: (7) Failed to connect to localhost port 5173Solution:
Update vite.config.ts:
export default defineConfig({ server: { host: "0.0.0.0", // Listen on all interfaces port: 5173, watch: { usePolling: true, // Required for Docker on some systems }, },});Update Dockerfile.dev:
EXPOSE 5173CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]Issue: Hot Module Replacement Not Working in Docker
Solution:
Add to vite.config.ts:
export default defineConfig({ server: { watch: { usePolling: true, interval: 1000, }, hmr: { host: "localhost", port: 5173, }, },});Python SaaS
Issue: SQLite Database Locked in Docker
Error:
sqlite3.OperationalError: database is lockedSolution:
Use PostgreSQL in Docker instead of SQLite:
DATABASE_URL=postgresql://postgres:postgres@db:5432/python_saas_devOr configure SQLite for concurrent access:
from sqlalchemy import create_engine, eventfrom sqlalchemy.engine import Engine
@event.listens_for(Engine, "connect")def set_sqlite_pragma(dbapi_conn, connection_record): cursor = dbapi_conn.cursor() cursor.execute("PRAGMA journal_mode=WAL") cursor.execute("PRAGMA busy_timeout=5000") cursor.close()Best Practices
1. Environment Variable Checklist
- All templates have
.env.examplefiles -
.envis in.gitignore - Required variables are documented with examples
- Secrets use strong generation methods (
openssl rand -hex 32) - Frontend variables start with
VITE_orPUBLIC_
2. Docker Development Setup
- Use health checks for dependencies (db, redis)
- Use named volumes for persistence
- Bind mount source code for hot reload
- Named networks for service communication
- Resource limits set appropriately
Example:
services: api: build: ./api volumes: - ./api:/app # Source code - bundle:/bundle # Gems (named volume) mem_limit: 512m cpus: 0.53. CORS Configuration
- CORS origins configurable via environment
- Allow credentials for auth cookies/tokens
- Development allows
localhoston all ports - Production restricts to specific domains
- Preflight (OPTIONS) requests handled
4. Authentication Best Practices
- JWT secret is configurable and secure
- Tokens have reasonable expiration (24 hours)
- Refresh token mechanism implemented
- Auth state persists across browser refresh
- Logout clears all auth artifacts
5. Database Management
- Migrations run automatically on startup
- Seed data available for development
- Database volumes persist data
- Connection pooling configured
- Health checks ping database
6. Testing Your Setup
Run this checklist after initial setup:
# 1. All services start without errorsdocker-compose up -ddocker-compose ps # All should be "Up"
# 2. Database is accessibledocker-compose exec db psql -U postgres -c "SELECT 1;"
# 3. API health check passescurl http://localhost:3000/health
# 4. Frontend loads in browseropen http://localhost:5173
# 5. Frontend can call backend# (Open browser console and run):fetch('http://localhost:3000/health').then(r => r.json()).then(console.log)
# 6. Authentication flow works# (Test login through UI)
# 7. Protected routes redirect when not authenticated# (Navigate to protected route while logged out)Real Error Messages & Solutions
Error 1: Connection Refused
Error: connect ECONNREFUSED 127.0.0.1:3000Diagnosis:
- API service not running
- Wrong URL (using localhost inside container)
Fix:
# Check if API is runningdocker-compose ps api
# Check API logsdocker-compose logs api
# If using container-to-container, use service nameVITE_BACKEND_URL=http://api:3000 # not localhostError 2: CORS Preflight Failure
Access to XMLHttpRequest at 'http://localhost:3000/api/v1/users' from origin'http://localhost:5173' has been blocked by CORS policy: Response to preflightrequest doesn't pass access control check: No 'Access-Control-Allow-Origin' headeris present on the requested resource.Diagnosis: CORS middleware not configured or origin not allowed.
Fix: See M-001 CORS Configuration
Error 3: Invalid JWT Signature
JWT::DecodeError (Signature verification failed):OpenSSL::PKey::RSAError: Neither PUB key nor PRIV key:Diagnosis:
- JWT secret mismatch between frontend and backend
- JWT secret changed after tokens were issued
- Missing JWT_SECRET environment variable
Fix:
# Verify JWT_SECRET is set consistentlydocker-compose exec api env | grep JWT_SECRET
# Regenerate JWT secretopenssl rand -hex 32
# Update in both frontend and backend .envJWT_SECRET=your_new_secret
# Rebuild and restartdocker-compose build apidocker-compose restart api
# Users will need to log in againError 4: Database Migration Pending
ActiveRecord::PendingMigrationErrorMigrations are pending. To resolve this issue, run: bin/rails db:migrate RAILS_ENV=developmentDiagnosis: New migrations exist that havenβt been run.
Fix:
# Run migrationsdocker-compose exec api rails db:migrate
# Or add to docker-entrypoint.sh to run automaticallybundle exec rails db:migrateError 5: Module Not Found (Node)
Error: Cannot find module '@/components/Dashboard'Diagnosis:
- TypeScript path aliases not configured
- Missing dependency
- Case-sensitive import
Fix:
Check vite.config.ts:
import { defineConfig } from "vite";import react from "@vitejs/plugin-react";import path from "path";
export default defineConfig({ plugins: [react()], resolve: { alias: { "@": path.resolve(__dirname, "./src"), }, },});Check tsconfig.json:
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } }}Error 6: Permission Denied (Linux)
Error: EACCES: permission denied, open '/app/package.json'Diagnosis: Volume mounted files have wrong ownership on Linux.
Fix: See B-002 Docker Volume Permission Errors
Getting Help
If youβve tried everything in this guide:
-
Check System Status
Terminal window docker-compose psdocker-compose logs --tail=100 -
Gather Diagnostic Info
Terminal window # Save to filedocker-compose ps > diagnostic.txtdocker-compose logs >> diagnostic.txtdocker system info >> diagnostic.txtenv | grep -i vite >> diagnostic.txt -
Submit Issue
- GitHub Issues: https://github.com/seed-source/foundry-meta/issues
- Include: OS, Docker version, error messages, docker-compose.yml
- Paste diagnostic.txt
-
Use Feedback Command
Terminal window sscli feedback bug
Appendix: Quick Reference
Port Reference
| Service | Internal Port | External Port | URL (Host) | URL (Container) |
|---|---|---|---|---|
| React Client | 5173 | 5173 | http://localhost:5173 | http://client:5173 |
| Rails API | 3000 | 3000 | http://localhost:3000 | http://api:3000 |
| Python SaaS | 8000 | 8000 | http://localhost:8000 | http://python:8000 |
| PostgreSQL | 5432 | 5432 | localhost:5432 | db:5432 |
| Redis | 6379 | 6379 | localhost:6379 | redis:6379 |
Environment Variable Prefixes
| Template | Required Prefix | Example |
|---|---|---|
| React (Vite) | VITE_ | VITE_API_URL |
| Astro | PUBLIC_ | PUBLIC_API_URL |
| Rails | None | DATABASE_URL |
| Python | None | DATABASE_URL |
Common Commands
# Start everythingdocker-compose up -d
# Restart single servicedocker-compose restart api
# View logs (live)docker-compose logs -f api
# Execute command in containerdocker-compose exec api bash
# Rebuild after code changedocker-compose build apidocker-compose up -d api
# Nuclear option (reset everything)docker-compose down -vdocker-compose up --buildDocument Version: 1.0.0 Maintained By: Seed & Source Team Feedback: github.com/seed-source/foundry-meta/issues