This document explains how to set up environment variables for development and production environments.
-
Backend:
cd backend cp .env.example .env.development # Update .env.development if needed (defaults work for local dev)
-
Frontend:
cd frontend cp .env.example .env.development # Update .env.development if needed (defaults work for local dev)
-
Root (for Docker Compose):
cp .env.example .env # Update .env if deploying with Docker
-
Backend:
cd backend cp .env.example .env.production # IMPORTANT: Update SECRET_KEY with a strong random value (minimum 32 characters) # IMPORTANT: Set DEBUG=false
-
Frontend:
cd frontend cp .env.example .env.production # Leave VITE_API_BASE_URL as /api (nginx will proxy to backend)
-
Root (for Docker Compose):
cp .env.example .env # Update all production values
The backend automatically detects which environment to use by checking the ENVIRONMENT environment variable.
File: backend/src/core/config.py
ENVIRONMENT = os.getenv("ENVIRONMENT", "development") # Check env variable, default to development
env_file_path = Path(f".env.{ENVIRONMENT}") # Load .env.{development|production}Priority:
- Check
ENVIRONMENTenvironment variable - Default to
developmentif not set - Load
.env.{ENVIRONMENT}file (e.g.,.env.developmentor.env.production) - Fallback to
.envif environment-specific file doesn't exist
Local Development (automatic):
# Defaults to development mode, loads .env.development
uvicorn app_main:app --reloadLocal Production Testing:
export ENVIRONMENT=production
uvicorn app_main:appDocker (automatic):
docker-compose up -d
# Sets ENVIRONMENT=production, loads .env.productionπ Backend running in development mode (loaded from .env.development)
Local frontend development with direct backend connection.
VITE_API_BASE_URL=http://localhost:8000/api
VITE_APP_NAME=Simple Invoicing
VITE_LOG_LEVEL=debugWhen to use: Running npm run dev locally, backend running on localhost:8000
Production frontend served via Nginx with API proxy.
VITE_API_BASE_URL=/api
VITE_APP_NAME=Simple Invoicing
VITE_LOG_LEVEL=errorWhen to use: Building with npm run build, served via Docker/Nginx
Note: Uses /api relative path because Nginx proxies /api/* requests to the backend container.
Local backend development with local PostgreSQL.
DATABASE_URL=postgresql://simple_user:simple_password@localhost:5432/simple_invoicing
SECRET_KEY=dev-secret-key-change-in-production
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
DEBUG=trueWhen to use: Running uvicorn app_main:app locally, PostgreSQL running on localhost:5432
Production backend running in Docker with remote database.
DATABASE_URL=postgresql://simple_user:simple_password@db:5432/simple_invoicing
SECRET_KEY=your-secure-secret-key-here-minimum-32-chars
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
DEBUG=falseWhen to use: Running via Docker Compose, DATABASE_URL points to internal db service
Important:
- Replace
SECRET_KEYwith a strong, randomly generated string (minimum 32 characters) - Set
DEBUG=falsein production - Use strong database credentials
Root-level environment file for Docker Compose orchestration.
FRONTEND_PORT=5173
VITE_API_BASE_URL=http://localhost:8000/api
BACKEND_PORT=8000
DATABASE_URL=postgresql://simple_user:simple_password@localhost:5432/simple_invoicing
POSTGRES_USER=simple_user
POSTGRES_PASSWORD=simple_password
POSTGRES_DB=simple_invoicing
COMPOSE_PROJECT_NAME=simple_invoicingWhen to use: Setting up Docker Compose environment
| Variable | Development | Production | Purpose |
|---|---|---|---|
VITE_API_BASE_URL |
http://localhost:8000/api |
/api |
Backend API endpoint |
VITE_APP_NAME |
Simple Invoicing |
Simple Invoicing |
App display name |
VITE_LOG_LEVEL |
debug |
error |
Console logging verbosity |
Vite Note: All frontend variables must be prefixed with VITE_ to be accessible in the code via import.meta.env.
| Variable | Development | Production | Purpose | Required |
|---|---|---|---|---|
DATABASE_URL |
postgresql://simple_user:simple_password@localhost:5432/simple_invoicing |
postgresql://simple_user:simple_password@db:5432/simple_invoicing |
PostgreSQL connection string | β |
SECRET_KEY |
dev-secret-key-change-in-production |
Strong random string (32+ chars) | JWT signing secret | β |
ALGORITHM |
HS256 |
HS256 |
JWT algorithm | β |
ACCESS_TOKEN_EXPIRE_MINUTES |
30 |
30 |
Token expiration time | β |
DEBUG |
true |
false |
Debug mode (detailed error messages) | β |
| Variable | Purpose |
|---|---|
POSTGRES_USER |
PostgreSQL username |
POSTGRES_PASSWORD |
PostgreSQL password |
POSTGRES_DB |
Default database name |
POSTGRES_PORT |
PostgreSQL port (5432) |
For production, generate a cryptographically strong secret:
# Using Python
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
# Using OpenSSL
openssl rand -hex 32
# Using /dev/urandom
head -c 32 /dev/urandom | base64Copy the output to backend/.env.production:
SECRET_KEY=your-generated-string-here-
Setup PostgreSQL (install locally or use Docker for DB only):
docker run -d --name postgres \ -e POSTGRES_USER=simple_user \ -e POSTGRES_PASSWORD=simple_password \ -e POSTGRES_DB=simple_invoicing \ -p 5432:5432 \ postgres:16-alpine
-
Backend:
cd backend cp .env.example .env.development python -m venv .venv source .venv/bin/activate pip install -r requirements.txt uvicorn app_main:app --reload
-
Frontend (new terminal):
cd frontend cp .env.example .env.development npm install npm run dev -
Access:
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
-
Setup:
cp .env.example .env # Edit .env if needed -
Start services:
docker-compose up -d
-
Access:
- Frontend: http://localhost (port 80)
- Backend API: http://localhost:8000
- Database: localhost:5432
-
Backend
.env.production:-
SECRET_KEYis strong (32+ random characters) -
DEBUG=false -
DATABASE_URLpoints to production database -
ACCESS_TOKEN_EXPIRE_MINUTESis appropriate (30-120)
-
-
Frontend
.env.production:-
VITE_API_BASE_URL=/api(or your domain) -
VITE_LOG_LEVEL=error
-
-
Docker Compose:
- Database credentials are strong
-
.envfile is not committed to version control - All services have proper resource limits
- Health checks pass
-
Security:
-
.envand.env.*files are in.gitignore - Database backups are configured
- HTTPS is enabled (via reverse proxy/CDN)
- CORS origins are restricted
-
Add these to .gitignore to prevent committing sensitive data:
# Environment files
.env
.env.*.local
.env.production
.env.development
# Node/Python
node_modules/
dist/
build/
.venv/
venv/
__pycache__/
# IDE
.vscode/
.idea/
*.swp
*.swo
- Check
VITE_API_BASE_URLis correct - In dev: ensure backend is running on
localhost:8000 - In Docker: ensure both services are on same network
- Check browser console for actual API URL being used
- Check
DATABASE_URLis correct and database is running - Check
SECRET_KEYis set - View logs:
docker-compose logs backend - Verify PostgreSQL is accessible
- Check
POSTGRES_USERandPOSTGRES_PASSWORDmatch - Verify PostgreSQL is running:
docker-compose logs db - Test connection:
psql postgresql://user:pass@localhost:5432/db
- Use localhost URLs for easy debugging
- Enable debug logs for troubleshooting
- Use short token expiration for testing
- Minimal security required
- Use subdomain/staging server URLs
- Enable limited debug logs
- Use reasonable token expiration (60 minutes)
- Use staging database credentials
- Use domain/CDN URLs
- Disable all debug logs
- Use high-security SECRET_KEY
- Use production database with backups
- Implement rate limiting
- Use HTTPS only
- Monitor logs and errors