Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Luisanchez0/modulo_Horario/llms.txt

Use this file to discover all available pages before exploring further.

Deploying Módulo Horario requires coordinating four microservices — usuarios-service, materias-service, aulas-service, and horario-service — along with MySQL, PostgreSQL, an Nginx reverse proxy, and SSL certificates. This guide walks you through every step in the order you should perform them.
Never run production with the default secrets from .env.example. Values like change_me_jwt_secret are publicly known. All secrets must be rotated before the first deployment and every 30 days thereafter.

Pre-deployment checklist

Before touching any server, confirm every item below is complete.
  • All tests passing in CI
  • All secrets rotated (JWT_SECRET, ADMIN_CREATION_KEY, INTERNAL_API_KEY, database passwords)
  • HTTPS/SSL configured and certificate issued
  • Rate limiting enabled in Nginx for /auth/login (5 req/min) and API endpoints (100 req/min)
  • Firewall rules applied — only ports 80 and 443 exposed externally
  • Centralized logging configured (ELK Stack or CloudWatch)
  • Database backup completed before deployment
  • Monitoring (Prometheus + Grafana) configured and alerting
1

Generate production secrets

Generate cryptographically random values for every secret. Never reuse development values.
# JWT signing key — must be 32+ bytes
openssl rand -hex 32

# Admin account creation key
openssl rand -hex 16

# Inter-service authentication key
openssl rand -hex 16
Store the generated values in AWS Secrets Manager (recommended) rather than a .env file.
Use AWS Secrets Manager to avoid storing secrets on disk. Create a single secret with all keys, then reference them in your docker-compose.yml via Docker secrets or an aws secretsmanager get-secret-value call at startup.
aws secretsmanager create-secret \
  --name microservicio-horario/jwt \
  --secret-string '{
    "JWT_SECRET": "<generated>",
    "ADMIN_CREATION_KEY": "<generated>",
    "INTERNAL_API_KEY": "<generated>"
  }'
2

Build and start all services

Copy the root environment template and fill in your generated secrets before building.
cp services/.env.example services/.env
# Edit services/.env with your production values
Build all four service images and start them in detached mode.
cd services
docker compose build
docker compose up -d
Confirm all containers started without errors.
docker compose ps
docker compose logs --tail=50 horario-service
3

Verify health endpoints

Each service exposes a GET /health endpoint. All four must return 200 OK before proceeding.
# usuarios-service
curl -s http://localhost:8001/health

# materias-service
curl -s http://localhost:8002/health

# aulas-service
curl -s http://localhost:8003/health

# horario-service
curl -s http://localhost:8004/health
Each should respond with {"status": "ok"} or a similar success payload. If any service returns an error, inspect its logs before continuing.
docker compose logs <service-name> | tail -100
4

Configure Nginx reverse proxy with SSL

Install Nginx and Certbot, then write the following configuration to /etc/nginx/nginx.conf. This configuration enforces HTTPS, proxies all four services, applies rate limiting, and sets security headers.
upstream horario_backend {
    server localhost:8004;
}
upstream usuarios_backend {
    server localhost:8001;
}
upstream materias_backend {
    server localhost:8002;
}
upstream aulas_backend {
    server localhost:8003;
}

server {
    listen 80;
    server_name api.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    limit_req_zone $binary_remote_addr zone=loginzone:10m rate=5r/m;
    limit_req_zone $binary_remote_addr zone=apizone:10m rate=100r/m;

    location /auth/login {
        limit_req zone=loginzone burst=10 nodelay;
        proxy_pass http://usuarios_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /horarios {
        limit_req zone=apizone burst=50 nodelay;
        proxy_pass http://horario_backend;
        proxy_set_header Host $host;
        proxy_set_header Authorization $http_authorization;
    }

    location /docentes {
        limit_req zone=apizone burst=50 nodelay;
        proxy_pass http://usuarios_backend;
    }

    location /api/v1/materias {
        limit_req zone=apizone burst=50 nodelay;
        proxy_pass http://materias_backend;
    }

    location /aulas {
        limit_req zone=apizone burst=50 nodelay;
        proxy_pass http://aulas_backend;
    }
}
5

Obtain an SSL certificate with Let's Encrypt

Install Certbot and issue a certificate for your domain. The --standalone flag temporarily binds to port 80, so run this before starting Nginx, or use --nginx if Nginx is already running.
sudo apt-get install certbot python3-certbot-nginx

sudo certbot certonly --standalone \
  -d api.yourdomain.com \
  -d www.yourdomain.com
Enable automatic renewal so certificates never expire.
sudo systemctl enable certbot.timer

# Test that renewal works
sudo certbot renew --dry-run
Restart Nginx after the certificate is in place.
sudo systemctl restart nginx
sudo systemctl status nginx
6

Set up the production database

Módulo Horario uses PostgreSQL for horario-service and materias-service, and MySQL for usuarios-service and aulas-service. For production, use managed database instances rather than the containers bundled in docker-compose.yml.
aws rds create-db-instance \
  --db-instance-identifier modulo-horario-pg \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --master-username postgres \
  --master-user-password '<STRONG_PASSWORD>' \
  --allocated-storage 20 \
  --publicly-accessible false

# Retrieve the endpoint after the instance is available
aws rds describe-db-instances \
  --db-instance-identifier modulo-horario-pg \
  --query 'DBInstances[0].Endpoint.Address'

# Run migrations for horario-service
export DATABASE_URL="postgresql://postgres:<password>@<rds-endpoint>:5432/horarios_db"
alembic upgrade head
Update DATABASE_URL in your secrets store to point to the new RDS endpoint before restarting services.
7

Post-launch verification

After everything is running, complete the following checks before declaring the deployment successful.Before go-live:
  • All four health endpoints return 200 OK through the Nginx proxy (HTTPS)
  • Login returns a JWT token at POST /auth/login
  • Security scan completed (OWASP ZAP or equivalent)
  • Load test passed: ab -n 1000 -c 10 https://api.yourdomain.com/horarios
  • Automated database backup configured
  • Monitoring alerts are firing correctly
  • Rollback runbook available and tested
  • On-call rotation configured
First week post-launch:
  • Monitor CPU, memory, and request latency
  • Review logs hourly for the first 24 hours, then daily
  • Confirm rate limiting is blocking excess requests on /auth/login
  • Plan scaling if load increases beyond tested thresholds

Build docs developers (and LLMs) love