A modern portfolio website for filmmakers and photographers built with React + Vite frontend and Python FastAPI backend.
- Portfolio Gallery: Display posts with images and videos
- Categories: Organize posts by categories
- Booking System: Allow clients to book sessions with conflict detection
- Admin Panel: Manage posts, bookings, and categories
- Media Management: Support for images and videos with metadata
- Responsive Design: Modern UI that works on all devices
- React 18
- Vite
- React Router
- Axios
- Python 3.11
- FastAPI
- SQLAlchemy
- PostgreSQL
- Docker & Docker Compose
portfolio/
├── backend/
│ ├── db/
│ │ └── init.sql # Database schema
│ ├── main.py # FastAPI application
│ ├── models.py # SQLAlchemy models
│ ├── schemas.py # Pydantic schemas
│ ├── database.py # Database connection
│ ├── Dockerfile # Backend Docker image
│ └── requirements.txt # Python dependencies
├── frontend/
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── pages/ # Page components
│ │ ├── api/ # API client
│ │ └── ...
│ ├── package.json
│ └── vite.config.js
└── docker-compose.yml # Docker orchestration
- posts: Portfolio posts with title, description, and status
- media: Images and videos linked to posts
- categories: Post categories
- post_categories: Many-to-many relationship
- bookings: Client booking requests with time slot validation
- Docker (version 20.10+) and Docker Compose (version 2.0+)
git clone <repository-url>
cd portfolioCreate a .env file in the root directory with the following variables:
# Cloudinary Configuration (for media uploads)
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
CLOUDINARY_UPLOAD_PRESET=your_upload_preset
# Admin Credentials
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_secure_password
SECRET_KEY=your-secret-key-for-jwt-change-in-productionNote: Make sure to use UTF-8 encoding (without BOM) when creating the .env file.
Start all services (PostgreSQL database, FastAPI backend, and React frontend):
docker-compose up -dThis will start:
- PostgreSQL database on port
5432 - FastAPI backend on port
8000 - React frontend on port
5173
The database schema is automatically initialized when the PostgreSQL container starts using backend/db/init.sql.
After starting Docker containers, access:
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- API Documentation (Swagger): http://localhost:8000/docs
- API Documentation (ReDoc): http://localhost:8000/redoc
- Admin Panel: http://localhost:5173/admin (requires login)
Use the credentials from your .env file:
- Username: Your configured
ADMIN_USERNAME(default:admin) - Password: Your configured
ADMIN_PASSWORD
GET /api/posts- List all postsGET /api/posts/{id}- Get post detailsPOST /api/posts- Create new postPUT /api/posts/{id}- Update postDELETE /api/posts/{id}- Delete post
POST /api/media- Add media to postGET /api/posts/{id}/media- Get post mediaDELETE /api/media/{id}- Delete media
GET /api/categories- List categoriesPOST /api/categories- Create categoryDELETE /api/categories/{id}- Delete category
GET /api/bookings- List bookingsGET /api/bookings/{id}- Get booking detailsPOST /api/bookings- Create booking (with conflict detection)PUT /api/bookings/{id}- Update bookingDELETE /api/bookings/{id}- Delete booking
| Variable | Description | Required |
|---|---|---|
CLOUDINARY_CLOUD_NAME |
Cloudinary cloud name | Yes |
CLOUDINARY_API_KEY |
Cloudinary API key | Yes |
CLOUDINARY_API_SECRET |
Cloudinary API secret | Yes |
CLOUDINARY_UPLOAD_PRESET |
Cloudinary upload preset | Yes |
ADMIN_USERNAME |
Admin login username | Yes |
ADMIN_PASSWORD |
Admin login password | Yes |
SECRET_KEY |
JWT secret key (change in production) | Yes |
POSTGRES_USER: postgresPOSTGRES_PASSWORD: 111111POSTGRES_DB: portfolio_dbDATABASE_URL: Automatically set in backend container
# Start all services
docker-compose up -d
# Stop all services
docker-compose down
# View logs
docker-compose logs -f backend
docker-compose logs -f db
# Rebuild containers after code changes
docker-compose up -d --build
# Stop and remove all containers and volumes
docker-compose down -v# Connect to PostgreSQL
docker-compose exec db psql -U postgres -d portfolio_db
# Run migrations manually
docker-compose exec -T db psql -U postgres -d portfolio_db < backend/migrations/add_featured_media.sql
# Backup database
docker-compose exec db pg_dump -U postgres portfolio_db > backup.sql
# Restore database
docker-compose exec -T db psql -U postgres -d portfolio_db < backup.sql# Access container shells
docker-compose exec backend bash
docker-compose exec frontend sh
# Check container status
docker-compose ps
# Restart specific services
docker-compose restart backend
docker-compose restart frontend
docker-compose restart db
# View logs for all services
docker-compose logs -f
# View logs for specific service
docker-compose logs -f frontend
docker-compose logs -f backend
docker-compose logs -f dbBackend won't start:
- Check if
.envfile exists and has correct values - Verify Docker containers are running:
docker-compose ps - Check backend logs:
docker-compose logs backend - Ensure port 8000 is not in use
- Rebuild containers:
docker-compose up -d --build
Database connection errors:
- Verify PostgreSQL container is healthy:
docker-compose ps db - Check database logs:
docker-compose logs db - Ensure database is initialized: Check
backend/db/init.sqlexists - Restart database:
docker-compose restart db
Frontend can't connect to backend:
- Verify both containers are running:
docker-compose ps - Check frontend logs:
docker-compose logs frontend - Check backend logs:
docker-compose logs backend - Ensure backend is accessible at http://localhost:8000
- Check CORS settings in
backend/main.py - Verify frontend proxy configuration in
frontend/vite.config.js
Upload errors:
- Verify Cloudinary credentials in
.envfile - Check Cloudinary upload preset is configured correctly
- Ensure
.envfile uses UTF-8 encoding (without BOM)
-
Update environment variables:
- Change
SECRET_KEYto a strong random value - Update
ADMIN_PASSWORDto a secure password - Verify all Cloudinary credentials are correct
- Change
-
Configure CORS:
- Update
CORS_ORIGINSinbackend/main.pyto your production domain - Remove development origins
- Update
-
Build frontend:
cd frontend npm run buildThe built files will be in
frontend/dist/ -
Set up reverse proxy:
- Use nginx or similar to serve frontend static files
- Proxy API requests to backend on port 8000
- Configure SSL certificates (Let's Encrypt recommended)
-
Database backup:
- Set up regular database backups
- Consider using managed PostgreSQL service for production
-
Docker Compose production:
- Create
docker-compose.prod.ymlwith production settings - Remove volume mounts for code (use built images)
- Set restart policies:
restart: unless-stopped
- Create
services:
db:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: portfolio_db
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
backend:
build:
context: ./backend
dockerfile: Dockerfile
restart: unless-stopped
env_file:
- .env
environment:
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/portfolio_db
depends_on:
db:
condition: service_healthy
volumes:
postgres_data:MIT