Skip to main content

Environment Variables

Overview

This guide provides a comprehensive reference for all environment variables used in the Ink platform. Learn how to configure different environments, manage secrets securely, and understand variable precedence.

Target Audience: Developers and DevOps engineers
Prerequisites: Basic understanding of environment variables and shell configuration
Estimated Time: 20-30 minutes

Prerequisites

  • Understanding of environment variables in Linux/macOS/Windows
  • Basic shell scripting knowledge
  • Familiarity with Docker and Docker Compose
  • Knowledge of application configuration

Variable Categories

Installation Steps

1. Create Environment File

# filepath: /Users/jetstart/dev/jetrev/ink/.env
# Application Configuration
SPRING_PROFILES_ACTIVE=local
SERVER_PORT=8081
CONTEXT_PATH=/
LOG_LEVEL=INFO
HIBERNATE_LOG_LEVEL=WARN
SHOW_SQL=false

# Database Configuration
DB_URL=jdbc:postgresql://localhost:5432/ink_database
DB_USERNAME=ink_user
DB_PASSWORD=ink_password
DB_POOL_SIZE=10
DB_POOL_MIN_IDLE=5

# Security Configuration
JWT_SECRET=your-256-bit-secret-key-change-in-production
JWT_EXPIRATION=86400000
KEYCLOAK_ISSUER_URI=http://localhost:8080/realms/ink
KEYCLOAK_JWK_URI=http://localhost:8080/realms/ink/protocol/openid-connect/certs
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8081

# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DATABASE=0

# Feature Flags
REGISTRATION_ENABLED=true
EMAIL_VERIFICATION_REQUIRED=false
TWO_FACTOR_ENABLED=false
RATE_LIMIT_ENABLED=true
RATE_LIMIT_RPM=100

# Logging
LOG_FILE=logs/ink-platform.log

2. Environment-Specific Files

# filepath: /Users/jetstart/dev/jetrev/ink/.env.local
# Local Development Environment
SPRING_PROFILES_ACTIVE=local
LOG_LEVEL=DEBUG
SHOW_SQL=true
HIBERNATE_LOG_LEVEL=DEBUG
REGISTRATION_ENABLED=true
EMAIL_VERIFICATION_REQUIRED=false
# filepath: /Users/jetstart/dev/jetrev/ink/.env.production
# Production Environment (DO NOT COMMIT)
SPRING_PROFILES_ACTIVE=prod
SERVER_PORT=8080
LOG_LEVEL=WARN
SHOW_SQL=false

DB_URL=jdbc:postgresql://prod-db-host:5432/ink_production
DB_USERNAME=${PROD_DB_USER}
DB_PASSWORD=${PROD_DB_PASSWORD}
DB_POOL_SIZE=50

JWT_SECRET=${PROD_JWT_SECRET}
KEYCLOAK_ISSUER_URI=https://auth.production.com/realms/ink
CORS_ALLOWED_ORIGINS=https://app.production.com

REDIS_HOST=prod-redis-host
REDIS_PASSWORD=${PROD_REDIS_PASSWORD}

EMAIL_VERIFICATION_REQUIRED=true
TWO_FACTOR_ENABLED=true

3. Docker Compose Integration

# filepath: /Users/jetstart/dev/jetrev/ink/docker-compose.yml
version: '3.8'

services:
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=${DB_USERNAME:-ink_user}
- POSTGRES_PASSWORD=${DB_PASSWORD:-ink_password}
- POSTGRES_DB=${POSTGRES_DB:-ink_database}
ports:
- "${DB_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data

keycloak:
image: quay.io/keycloak/keycloak:23.0
environment:
- KEYCLOAK_ADMIN=${KEYCLOAK_ADMIN:-admin}
- KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD:-admin}
- KC_DB=postgres
- KC_DB_URL=${KEYCLOAK_DB_URL:-jdbc:postgresql://postgres:5432/keycloak}
- KC_DB_USERNAME=${DB_USERNAME:-ink_user}
- KC_DB_PASSWORD=${DB_PASSWORD:-ink_password}
command: start-dev
ports:
- "${KEYCLOAK_PORT:-8080}:8080"
depends_on:
- postgres

redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD:-}
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data

volumes:
postgres_data:
redis_data:

Configuration

Core Application Variables

VariableDefaultDescriptionRequired
SPRING_PROFILES_ACTIVElocalActive Spring profileYes
SERVER_PORT8081Application server portNo
CONTEXT_PATH/Application context pathNo
LOG_LEVELINFOApplication log levelNo
LOG_FILElogs/ink-platform.logLog file pathNo

Database Variables

VariableDefaultDescriptionRequired
DB_URLjdbc:postgresql://localhost:5432/ink_databaseDatabase JDBC URLYes
DB_USERNAMEink_userDatabase usernameYes
DB_PASSWORDink_passwordDatabase passwordYes
DB_POOL_SIZE10Max connection pool sizeNo
DB_POOL_MIN_IDLE5Minimum idle connectionsNo

Security Variables

VariableDefaultDescriptionRequired
JWT_SECRET-JWT signing secret (256-bit)Yes
JWT_EXPIRATION86400000JWT expiration (ms)No
KEYCLOAK_ISSUER_URIhttp://localhost:8080/realms/inkKeycloak issuer URIYes
KEYCLOAK_JWK_URIAuto-derivedKeycloak JWK set URINo
CORS_ALLOWED_ORIGINShttp://localhost:3000Allowed CORS originsYes

Redis Variables

VariableDefaultDescriptionRequired
REDIS_HOSTlocalhostRedis hostYes
REDIS_PORT6379Redis portNo
REDIS_PASSWORDEmptyRedis passwordNo
REDIS_DATABASE0Redis database indexNo

Feature Flag Variables

VariableDefaultDescriptionRequired
REGISTRATION_ENABLEDtrueEnable user registrationNo
EMAIL_VERIFICATION_REQUIREDfalseRequire email verificationNo
TWO_FACTOR_ENABLEDfalseEnable 2FANo
RATE_LIMIT_ENABLEDtrueEnable rate limitingNo
RATE_LIMIT_RPM100Requests per minute limitNo

Usage Examples

Loading Environment Variables

# Load from .env file
export $(cat .env | xargs)

# Load from profile-specific file
export $(cat .env.local | xargs)

# Verify loaded variables
echo $SPRING_PROFILES_ACTIVE
echo $DB_URL

Running with Environment Variables

# Using export
export SPRING_PROFILES_ACTIVE=dev
export SERVER_PORT=8082
mvn spring-boot:run

# Inline variables
SPRING_PROFILES_ACTIVE=dev SERVER_PORT=8082 mvn spring-boot:run

# Using .env file with Docker
docker compose --env-file .env.local up -d

Programmatic Access

// filepath: /Users/jetstart/dev/jetrev/ink/src/main/java/com/jetrev/ink/config/EnvironmentConfig.java
@Configuration
public class EnvironmentConfig {

@Value("${SPRING_PROFILES_ACTIVE:local}")
private String activeProfile;

@Autowired
private Environment environment;

@Bean
public String getEnvironmentInfo() {
return String.format(
"Profile: %s, Port: %s",
activeProfile,
environment.getProperty("SERVER_PORT", "8081")
);
}
}

Verification

Verification Script

# filepath: /Users/jetstart/dev/jetrev/ink/scripts/verify-env.sh
#!/bin/bash

echo "Verifying environment variables..."

# Check required variables
REQUIRED_VARS=(
"SPRING_PROFILES_ACTIVE"
"DB_URL"
"DB_USERNAME"
"DB_PASSWORD"
"JWT_SECRET"
"KEYCLOAK_ISSUER_URI"
)

MISSING_VARS=()

for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var}" ]; then
MISSING_VARS+=("$var")
else
echo "✓ $var is set"
fi
done

if [ ${#MISSING_VARS[@]} -ne 0 ]; then
echo ""
echo "❌ Missing required variables:"
printf ' - %s\n' "${MISSING_VARS[@]}"
exit 1
fi

echo ""
echo "✅ All required environment variables are set"

Testing Environment Configuration

// filepath: /Users/jetstart/dev/jetrev/ink/src/test/java/com/jetrev/ink/config/EnvironmentTest.java
@SpringBootTest
@ActiveProfiles("test")
class EnvironmentTest {

@Autowired
private Environment environment;

@Test
void testRequiredVariablesPresent() {
assertThat(environment.getProperty("spring.datasource.url"))
.isNotBlank();
assertThat(environment.getProperty("spring.datasource.username"))
.isNotBlank();
}

@Test
void testProfileConfiguration() {
String[] profiles = environment.getActiveProfiles();
assertThat(profiles).contains("test");
}
}

Troubleshooting

Variable Not Being Recognized

# Check if variable is exported
env | grep SPRING_PROFILES_ACTIVE

# Source .env file properly
set -a
source .env
set +a

# Verify in Spring Boot
curl http://localhost:8081/actuator/env | grep SPRING_PROFILES_ACTIVE

Docker Compose Variable Issues

# Verify Docker Compose reads .env
docker compose config

# Check specific service environment
docker compose exec app env | grep DB_URL

# Debug variable expansion
docker compose --env-file .env.local config

Precedence Conflicts

// filepath: /Users/jetstart/dev/jetrev/ink/src/main/java/com/jetrev/ink/debug/VariableDebugger.java
@Component
@Slf4j
public class VariableDebugger implements ApplicationRunner {

@Autowired
private Environment environment;

@Override
public void run(ApplicationArguments args) {
log.info("=== Variable Sources ===");

ConfigurableEnvironment env = (ConfigurableEnvironment) environment;
MutablePropertySources sources = env.getPropertySources();

sources.forEach(source -> {
log.info("Source: {} ({})", source.getName(), source.getClass().getSimpleName());
});

// Check specific property
String dbUrl = environment.getProperty("spring.datasource.url");
log.info("Final DB URL: {}", dbUrl);
}
}

Code Examples

Environment-Based Bean Configuration

// filepath: /Users/jetstart/dev/jetrev/ink/src/main/java/com/jetrev/ink/config/EnvBasedConfig.java
@Configuration
public class EnvBasedConfig {

@Value("${SPRING_PROFILES_ACTIVE:local}")
private String profile;

@Bean
@ConditionalOnProperty(name = "REGISTRATION_ENABLED", havingValue = "true")
public RegistrationService registrationService() {
return new RegistrationService();
}

@Bean
public DataSource dataSource(
@Value("${DB_URL}") String url,
@Value("${DB_USERNAME}") String username,
@Value("${DB_PASSWORD}") String password,
@Value("${DB_POOL_SIZE:10}") int poolSize) {

HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(poolSize);

return new HikariDataSource(config);
}
}

Secure Variable Handling

// filepath: /Users/jetstart/dev/jetrev/ink/src/main/java/com/jetrev/ink/security/SecretValidator.java
@Component
public class SecretValidator implements InitializingBean {

@Value("${JWT_SECRET}")
private String jwtSecret;

@Value("${SPRING_PROFILES_ACTIVE}")
private String profile;

@Override
public void afterPropertiesSet() {
// Validate JWT secret length
if (jwtSecret.length() < 32) {
throw new IllegalStateException(
"JWT_SECRET must be at least 32 characters");
}

// Warn about default secrets in production
if ("prod".equals(profile) &&
jwtSecret.contains("changeme")) {
throw new IllegalStateException(
"Default JWT_SECRET detected in production!");
}
}
}

Best Practices

  1. Never Commit Secrets: Add .env* to .gitignore
  2. Use Default Values: Provide sensible defaults for non-sensitive variables
  3. Validate Required Variables: Check presence on startup
  4. Document All Variables: Maintain up-to-date documentation
  5. Use Strong Secrets: Generate cryptographically secure values
  6. Rotate Credentials: Implement regular rotation for production
  7. Profile Separation: Keep profile-specific variables isolated
  8. Environment Isolation: Use different credentials per environment

.gitignore Configuration

# filepath: /Users/jetstart/dev/jetrev/ink/.gitignore
# ...existing code...

# Environment files
.env
.env.local
.env.*.local
.env.production
.env.staging
*.env

Secret Generation

# Generate JWT secret
openssl rand -base64 32

# Generate secure password
openssl rand -base64 24

# Generate UUID
uuidgen

Performance Optimization

Connection Pool Tuning

# High-traffic configuration
DB_POOL_SIZE=50
DB_POOL_MIN_IDLE=20
DB_POOL_MAX_LIFETIME=1800000
DB_POOL_CONNECTION_TIMEOUT=20000

Redis Optimization

# Production Redis settings
REDIS_MAX_ACTIVE=20
REDIS_MAX_IDLE=10
REDIS_MIN_IDLE=5
REDIS_MAX_WAIT=-1

Additional Resources


Next Steps: Learn about Keycloak Integration for authentication and authorization.