Environment Variables Reference
Complete reference for all environment variables used in the Portugal Odyssey platform.
Overview
Environment variables are organized by:
- Shared variables - Used across all environments
- Environment-specific - Dev, Qual, or Prod specific
- Service-specific - Variables for individual services
Template Files
Environment templates are located in infrastructure/env-templates/:
env.shared.template - Shared variables
.env.development - Development environment
env.qualification.template - Qualification environment
env.production.template - Production environment
Shared Variables
Infrastructure
| Variable |
Description |
Example |
POSTGRES_USER |
PostgreSQL admin user |
postgres |
POSTGRES_PASSWORD |
PostgreSQL admin password |
secure-password |
POSTGRES_DB |
Default database name |
portugal_odyssey |
REDIS_PASSWORD |
Redis password |
redis-password |
RABBITMQ_USER |
RabbitMQ admin user |
admin |
RABBITMQ_PASSWORD |
RabbitMQ admin password |
rabbitmq-password |
Registry & Deployment
| Variable |
Description |
Example |
REGISTRY_IMAGE |
Container registry prefix |
registry.gitlab.com/portugalodissey/po-platform |
REGISTRY_USER |
Registry username |
gitlab-username |
REGISTRY_PASSWORD |
Registry password/token |
glpat-xxxxx |
DEPLOY_QUAL_HOST |
Qualification VPS hostname |
qual.portugalodyssey.pt |
DEPLOY_PROD_HOST |
Production VPS hostname |
prod.portugalodyssey.pt |
DEPLOY_USER |
SSH user for deployment |
root |
Environment-Specific Variables
Development (ENV=dev)
| Variable |
Description |
Default |
POSTGRES_USER_DEV |
Dev PostgreSQL user |
postgres |
POSTGRES_PASSWORD_DEV |
Dev PostgreSQL password |
devpassword |
RABBITMQ_USER_DEV |
Dev RabbitMQ user |
admin |
RABBITMQ_PASSWORD_DEV |
Dev RabbitMQ password |
devpassword |
Qualification (ENV=qual)
| Variable |
Description |
Example |
POSTGRES_USER_QUAL |
Qual PostgreSQL user |
postgres_qual |
POSTGRES_PASSWORD_QUAL |
Qual PostgreSQL password |
secure-password |
RABBITMQ_USER_QUAL |
Qual RabbitMQ user |
admin |
RABBITMQ_PASSWORD_QUAL |
Qual RabbitMQ password |
secure-password |
JWT_SECRET_QUAL |
JWT secret for qual |
qual-jwt-secret |
Production (ENV=prod)
| Variable |
Description |
Example |
POSTGRES_USER |
Prod PostgreSQL user |
postgres |
POSTGRES_PASSWORD |
Prod PostgreSQL password |
very-secure-password |
RABBITMQ_USER |
Prod RabbitMQ user |
admin |
RABBITMQ_PASSWORD |
Prod RabbitMQ password |
very-secure-password |
JWT_SECRET |
JWT secret for prod |
prod-jwt-secret |
Service-Specific Variables
API Gateway
| Variable |
Description |
Example |
PORT |
Service port |
3000 |
NODE_ENV |
Node environment |
production |
Auth Service
| Variable |
Description |
Example |
KEYCLOAK_URL |
Keycloak server URL |
https://keycloak.portugalodyssey.pt |
KEYCLOAK_REALM |
Keycloak realm |
portugal-odyssey |
KEYCLOAK_CLIENT_ID |
Keycloak client ID |
auth-service |
KEYCLOAK_CLIENT_SECRET |
Keycloak client secret |
client-secret |
JWT_SECRET |
JWT signing secret |
jwt-secret-key |
REDIS_URL |
Redis connection URL |
redis://redis:6379 |
Notification Service
| Variable |
Description |
Example |
DATABASE_URL |
PostgreSQL connection string |
postgresql://user:pass@postgres:5432/notification |
RABBITMQ_URL |
RabbitMQ connection URL |
amqp://user:pass@rabbitmq:5672/vhost |
SMTP_HOST |
SMTP server hostname |
smtp.example.com |
SMTP_PORT |
SMTP server port |
587 |
SMTP_USER |
SMTP username |
smtp-user |
SMTP_PASSWORD |
SMTP password |
smtp-password |
EMAIL_FROM |
Default sender email |
noreply@portugalodyssey.pt |
CONTACT_FROM_EMAIL |
Contact-form sender-confirmation from |
noreply@portugalodyssey.pt |
CONTACT_ADMIN_EMAIL |
Contact-form admin-notification recipient |
contact@portugalodyssey.pt |
Mail addresses must use the apex portugalodyssey.pt (Riff #235, 2026-06-02). The
qual. / mail. subdomains have no MX — mail to them blackholes, and they aren't
Resend-verified for sending (from on qual. caused a 550 bounce, #30). This applies to
the *_QUAL / *_PROD variants too. Only the apex receives (→ mailcow) and sends (Resend).
Payment Service
| Variable |
Description |
Example |
STRIPE_SECRET_KEY |
Stripe secret key |
sk_live_xxxxx |
STRIPE_PUBLISHABLE_KEY |
Stripe publishable key |
pk_live_xxxxx |
STRIPE_WEBHOOK_SECRET |
Stripe webhook secret |
whsec_xxxxx |
File Service
The platform uses self-hosted MinIO across all envs (W2 B-2, Riff #36, 2026-04-30); the AWS S3 path was retired since prod adopted MinIO with its own per-env volume. The aws-sdk S3 client still drives the wire — it just talks to MinIO at http://minio-{qual,prod}:9000.
| Variable |
Description |
Example |
FILE_PROVIDER |
Storage provider |
minio (other values s3, gcs, local are still in the code path but unused in deployed envs) |
S3_ENDPOINT |
MinIO endpoint (internal, per-env) |
http://minio-qual:9000, http://minio-prod:9000 |
S3_ACCESS_KEY |
MinIO root user |
${MINIO_ROOT_USER_QUAL} / ${MINIO_ROOT_USER_PROD} |
S3_SECRET_KEY |
MinIO root password |
${MINIO_ROOT_PASSWORD_QUAL} / ${MINIO_ROOT_PASSWORD_PROD} |
S3_REGION |
Required by aws-sdk; not enforced by MinIO |
us-east-1 |
S3_BUCKET |
Bucket name (per-env) |
po-files-qual, po-files-prod |
S3_PUBLIC_URL |
Public-facing URL prefix for media references |
https://files.qual.portugalodyssey.pt/po-files-qual, https://files.portugalodyssey.pt/po-files-prod |
Mail (Mailcow self-hosted; live since 2026-05-12)
Mailcow runs under compose project po-mail on the qual VPS, isolated from the rest of the platform. Variables live in /opt/mailcow-dockerized/mailcow.conf (Mailcow's native config file) AND /opt/po-platform/.env.qual (for the Resend smarthost key shared with notification-service + Keycloak). See ADR-009 and the mailcow operations runbook.
| Variable |
Location |
Description |
Example |
MAILCOW_HOSTNAME |
mailcow.conf |
MTA hostname; published as MX target |
mx.portugalodyssey.pt |
ADDITIONAL_SERVER_NAMES |
mailcow.conf |
Extra hostnames nginx-mailcow accepts (admin/webmail/autoconfig/autodiscover) |
mail-admin.portugalodyssey.pt,webmail.portugalodyssey.pt,autoconfig.portugalodyssey.pt,autodiscover.portugalodyssey.pt |
HTTP_PORT / HTTPS_PORT |
mailcow.conf |
nginx-mailcow exposed ports (Traefik connects on 9443) |
9080 / 9443 |
SKIP_LETS_ENCRYPT |
mailcow.conf |
Disable Mailcow's bundled ACME (Traefik owns public certs) |
y |
MAILCOW_ADMIN_PASSWORD_QUAL |
.env.qual (VPS) |
Mailcow admin UI password (mail-admin.portugalodyssey.pt) |
(secret, rotated quarterly) |
RESEND_API_KEY_QUAL |
.env.qual (VPS) |
Resend API key — used by Mailcow smarthost + notification-service + Keycloak |
re_xxxxxxxx |
Postfix relayhost |
inside Mailcow |
Resend smarthost endpoint |
[smtp.resend.com]:587 |
Postfix smtp_sasl_password_maps |
inside Mailcow |
SASL plain auth using apikey + RESEND_API_KEY_QUAL |
(operator config in Mailcow admin → Configuration → Routing) |
Note: raw SMTP/IMAP ports (25/465/587/993/4190) bind to the host directly and bypass Traefik. The Resend smarthost key is the only mail-related secret that needs rotation coordination across three consumers (Mailcow + notification-service + Keycloak) — see the runbook's "Rotate the Resend smarthost API key" section.
Setting Up Environment Variables
1. Copy Templates
cp infrastructure/env-templates/.env.development .env.dev
cp infrastructure/env-templates/env.qualification.template .env.qual
cp infrastructure/env-templates/env.production.template .env.prod
2. Edit Configuration
nano .env.dev # Edit development variables
3. Generate Secrets
make secrets # Generates secure random secrets
4. Verify Configuration
# Check variables are set
docker compose config
Security Best Practices
- Never commit
.env files - They're in .gitignore
- Use strong passwords - Generate secure random passwords
- Rotate secrets regularly - Especially in production
- Use different secrets per environment - Never reuse production secrets
- Limit access - Only grant access to those who need it
See Also