A professional portfolio and blog platform for developers and technology consultants. The application is built on Django 5.2, ships with a modern front-end, and includes tooling to manage projects, blog posts, skills, languages, and resumes from a friendly admin dashboard. English and Spanish translations are handled through django-parler with optional LibreTranslate automation.
Important: All runtime configuration (branding, personal data, content catalogs, etc.) is managed through environment variables and the Django admin. Edit the codebase only when you want to extend features or adjust the layout.
- GitHub: https://github.com/henfrydls/Portafolio-Manager.git
- Default branch:
main
- Responsive portfolio layout with fixed sidebar navigation.
- Admin analytics dashboard summarizing visits, inbox messages, and catalog stats.
- Project catalog with rich metadata, ordering, visibility control, and knowledge base links.
- Medium-style blog with categories, tags, featured images, and publication workflow.
- Resume module that renders a public HTML profile and generates PDF downloads.
- Secure contact form with rate limiting, server-side validation, and inbox management.
- Automatic translation pipeline (EN/ES) powered by django-parler and LibreTranslate.
- Hardened uploads with file-type checks, image optimization, and safe storage.
- SEO helpers including sitemaps, canonical metadata, and structured data snippets.
- Django 5.2 LTS
- SQLite for local development (swap for PostgreSQL in production)
- Bootstrap-based theme bundled with WhiteNoise for static delivery
- Pillow for image processing
- Optional Docker Compose stack for LibreTranslate integration
- Python 3.10+
- pip
- Git
- (optional) Docker Desktop 4.0+ if you plan to run the compose stack
# Clone the repository
git clone https://github.com/henfrydls/Portafolio-Manager.git
cd Portafolio-Manager
# Create a virtual environment
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
# Install dependencies
pip install --upgrade pip
pip install -r requirements/development.txtCopy the example environment file and customise the values:
copy .env.example .env # Windows
# cp .env.example .env # macOS / LinuxMinimum variables to review:
SECRET_KEY: generate a unique value (python -c "from django.core.management.utils import get_random_secret_key as g; print(g())")DEBUG=Truefor local developmentALLOWED_HOSTS=localhost,127.0.0.1- Email settings if you need outbound notifications
Initialize the database and start the development server:
python manage.py migrate
python manage.py createsuperuser
python manage.py collectstatic --noinput
python manage.py runserverVisit the following URLs:
- Portfolio: http://127.0.0.1:8000/
- Admin dashboard: http://127.0.0.1:8000/admin/
- Requisitos: Python 3.10+, pip, Git. Opcional: Docker Desktop si usarás Compose.
- Local (virtualenv):
python -m venv .venv source .venv/bin/activate pip install -r requirements/development.txt cp .env.example .env python manage.py migrate && python manage.py collectstatic --noinput python manage.py runserver
- Docker Compose (Postgres + Redis + LibreTranslate):
cp .env.example .env # opcional: ajusta DATABASE_URL/REDIS_URL en .env docker compose up --build - Docker Compose con Nginx (staging/prod-like):
# staging: expone nginx en http://127.0.0.1:80 (puede requerir permisos admin) docker compose -f docker-compose.yml --profile staging up --build # producción: expone nginx en puertos 80/443 docker compose -f docker-compose.yml --profile prod up --build
- Producción / staging (EC2 + Gunicorn + Nginx, Postgres, Redis):
- Define
.envconDJANGO_SETTINGS_MODULE=config.settings.production,DATABASE_URL(Postgres/RDS),REDIS_URL, dominios, correo, yENABLE_SSL=True(para dominios con certificado SSL). - En el servidor: instala deps del sistema, crea venv,
pip install -r requirements/base.txt. - Ejecuta
python manage.py collectstatic --noinput && python manage.py migratey crea superusuario o usapopulate_test_data. - Levanta Gunicorn y configura Nginx como proxy con TLS.
- Para staging usa
DJANGO_SETTINGS_MODULE=config.settings.stagingy variablesSTAGING_DOMAIN,ALLOWED_HOSTS_STAGING,CSRF_TRUSTED_ORIGINS_STAGING,DATABASE_URL/REDIS_URLde tu entorno de pruebas, yENABLE_SSL=False(si usas IP sin certificado). Puedes usar el perfil--profile stagingen Compose para simular el proxy Nginx en http://127.0.0.1:80.
- Define
Run the full stack with Django port 8000 exposed for direct access:
cp .env.example .env # or use copy on Windows
# Optional: override DATABASE_URL/REDIS_URL in .env for local
docker compose up --buildServices in Development:
- Django app (Gunicorn): http://127.0.0.1:8000/ ✅ Direct access
- Postgres:
dbservice (internal only) - Redis:
redisservice (internal only) - LibreTranslate: internal only (web reaches it at
http://libretranslate:5000) - Nginx config template: see
deploy/nginx.conf(replaceexample.comand uncomment TLS block for production)
How it works: The docker-compose.override.yml file automatically exposes port 8000 in development mode. This file is automatically merged when you run docker compose up.
Run with Nginx as reverse proxy (no direct Django access):
IMPORTANT: Must use -f docker-compose.yml to ignore the override file.
# Staging (MUST include -f to ignore override)
docker compose -f docker-compose.yml --profile staging up --build
# Production (MUST include -f to ignore override)
docker compose -f docker-compose.yml --profile prod up --buildServices in Staging/Prod:
- Django app (Gunicorn): Port 8000 (internal only) ❌ No direct access
- Nginx: http://127.0.0.1:80/ or http://127.0.0.1/ ✅ Only access point (may require admin privileges)
- Postgres:
dbservice (internal only) - Redis:
redisservice (internal only) - LibreTranslate: internal only
How it works: Using -f docker-compose.yml explicitly ignores the override file, so port 8000 is NOT exposed to the host. All traffic goes through Nginx on port 80.
Note: Port 80 may require administrator/root privileges on some systems. See troubleshooting section if you get "Permission denied" errors.
Question: Why use port 80 locally and how does it relate to production URLs without ports?
Answer: HTTP/HTTPS have default ports that browsers automatically use:
| Protocol | Default Port | What You Type | What Browser Uses |
|---|---|---|---|
| HTTP | 80 | http://tudominio.com |
http://tudominio.com:80 |
| HTTPS | 443 | https://tudominio.com |
https://tudominio.com:443 |
Local Staging (may require admin privileges):
http://localhost:80/ or http://localhost/
- Port 80 is the standard HTTP port (same as production)
- Nginx listens on port 80 and forwards to Django on port 8000 (internal)
- Note: Port 80 may require administrator/root privileges to bind
Production (Real Server):
https://tudominio.com/ (automatically uses port 443)
- Nginx listens on port 443 (HTTPS) with SSL certificate
- Nginx forwards requests to Django on port 8000 (internal, not exposed)
- Users never see
:443because it's the default HTTPS port
The Architecture:
┌─────────────────────────────────────────────────────────────────┐
│ DEVELOPMENT │
├─────────────────────────────────────────────────────────────────┤
│ Browser → http://localhost:8000 → Django (direct) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ STAGING (Local Testing) │
├─────────────────────────────────────────────────────────────────┤
│ Browser → http://localhost:80 → Nginx → Django (port 8000) │
│ (port 8000 NOT accessible from outside) │
│ (port 80 may require admin privileges) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ PRODUCTION (Real Server) │
├─────────────────────────────────────────────────────────────────┤
│ Browser → https://tudominio.com → Nginx:443 → Django (port 8000)│
│ (port 8000 NOT accessible from internet) │
│ (port 443 is default HTTPS, so users don't type it) │
└─────────────────────────────────────────────────────────────────┘
Key Takeaways:
- Development: Direct access to Django on port 8000 for debugging
- Staging: Production-like with Nginx on port 80 (may need admin privileges locally)
- Production: Nginx on port 443 (HTTPS) - invisible to users because it's the default
Data volumes:
- Postgres:
pgdata - Redis:
redisdata
Useful commands:
# Run migrations (already executed on startup, but re-run manually if needed)
docker compose exec web python manage.py migrate
# Seed demo data
docker compose exec web python manage.py populate_test_data
# Create superuser
docker compose exec web python manage.py createsuperuser
# Environment sanity check (environment-aware, validates only relevant vars)
docker compose exec web python manage.py check_env
# With Nginx profile (staging/prod-like) listening on port 80
# docker compose -f docker-compose.yml --profile staging up --build
# If you need to test LibreTranslate manually, temporarily expose its port in docker-compose.yml:
# libretranslate:
# ports:
# - "5000:5000"
# and revert it when done to keep it internal.Stop the stack with docker compose down (add -v to drop data volumes).
- Sign in to the Django admin and complete your Profile record.
- Review the autogenerated catalog entries (project types, categories, knowledge bases). They are seeded via migrations; if you re-run migrations on a fresh database you will always get exactly one record per slug.
- Add projects, blog posts, skills, experiences, languages, and resume entries.
- Configure Dashboard -> Settings to enable automatic translations. Point
translation_api_urlto LibreTranslate (local or hosted) and toggleauto_translate_enabled. - Test the public website and contact form to confirm emails and analytics tracking are working.
DOCKER_COMMANDS.md: quick reference for Docker Compose commands (development, staging, production).docs/SETUP.md: detailed installation, troubleshooting, and maintenance guide.docs/CONFIGURATION_GUIDE.md: environment variables, production hints, and security settings.docs/ADMIN_USAGE.md: workflows for managing profile, projects, blog posts, and catalogs.docs/EMAIL_SETUP.md: SMTP configuration for Gmail, Outlook, and custom providers.docs/TEST_DATA.md: using the management command to populate sample content.docs/DOCUMENTATION_INDEX.md: master table of contents that links every guide.
- Run tests:
python manage.py test - Seed demo content (local shell):
python manage.py populate_test_data - Seed demo content (Docker):
docker compose exec web python manage.py populate_test_data - Reset demo content: delete
db_development.sqlite3or runpython manage.py populate_test_data --reset - Review translation status:
python verify_translations.py
- Set
DEBUG=False, configure strongSECRET_KEY, and lock downALLOWED_HOSTS. - Switch the database to a managed service (PostgreSQL recommended) and update
.env. - Use Redis for cache/sessions/rate limits when available (
REDIS_URL,USE_CACHE_SESSIONS). - Serve static files via Nginx + Gunicorn (or keep WhiteNoise) and enable HTTPS/TLS.
- Configure media storage (local disk for small installs, or S3/Blob + CDN).
- Use HTTPS and apply Django security middleware settings (
SECURE_*,SESSION_COOKIE_SECURE, etc.). - Provision a robust email backend (e.g., SendGrid, Mailgun, SES).
- Add monitoring/logging (Sentry, ELK, or similar) as part of your deployment pipeline.
- Copy
.env.exampleto.env, setDJANGO_SETTINGS_MODULE=config.settings.production,DATABASE_URL(PostgreSQL/RDS),DB_SSL_REQUIRED=Trueif needed,REDIS_URL,ALLOWED_HOSTS_PROD,CSRF_TRUSTED_ORIGINS_PROD,PRODUCTION_DOMAIN, and email creds. - Install system deps:
sudo apt-get install build-essential libpq-dev nginx(pluscertbotif using Let’s Encrypt). - Install Python deps:
python -m venv .venv && source .venv/bin/activate && pip install --upgrade pip && pip install -r requirements/base.txt. - Build assets/data:
python manage.py collectstatic --noinput && python manage.py migrateand create an admin user (createsuperuser) or seed demo data (populate_test_data). - Run Gunicorn (example):
gunicorn config.wsgi:application --bind unix:/run/portfolio.sock --workers 3 --timeout 60. - Nginx (example): serve
/staticand/media, proxy/tounix:/run/portfolio.sock, setproxy_set_header X-Forwarded-Proto https, and enable TLS. - Repeat with separate
.env/RDS/Redis for staging usingDJANGO_SETTINGS_MODULE=config.settings.stagingand a different domain.
Issues and pull requests are welcome. Please describe the motivation for changes and reference any relevant documentation updates in your PR.
This project is available under the MIT License. See LICENSE for the full text.