RizTech Academy logo
RizTech Academy
Configuration and DeploymentLesson 1 of 430 min

Configuration, profiles and environment variables

Deploying means running the same application in a different environment — production — with different configuration: a real database, real secrets, stricter settings, less logging. This lesson is how to configure a Spring Boot application per environment cleanly, so the same build runs everywhere and the production-specific values come from outside the code. It reprises the best-practices configuration lesson from the deployment angle: what actually changes when you go live.

The same build, different configuration

The foundational deployment principle: you build the application once and run that same artifact in every environment, feeding in different configuration each time. You do not build a "production version" of the code — you build one jar and give it production values. This is what makes deployments reproducible and rollbacks trivial: the artifact tested in staging is the exact one that runs in production, differing only in the configuration fed to it.

Spring Boot reads configuration from application.properties/.yml and lets environment variables and command-line arguments override those, in a defined precedence (env vars and CLI args win). So the file holds sensible defaults, and production overrides the per-environment and secret values from the environment. The code never changes between environments.

Profiles for per-environment settings

Profiles give each environment its own configuration file:

application.properties            # shared defaults
application-dev.properties        # local development
application-prod.properties       # production

Activate with SPRING_PROFILES_ACTIVE=prod (an environment variable, the usual production mechanism) or --spring.profiles.active=prod. Spring loads application.properties plus the active profile's file, the profile overriding. So production settings live in application-prod.properties:

# application-prod.properties
spring.jpa.hibernate.ddl-auto=validate         # NOT update/create — Flyway owns the schema
spring.datasource.url=${DATABASE_URL}          # from the environment
logging.level.root=INFO                        # less noise than dev's DEBUG
server.error.include-stack-trace=never          # never leak stack traces to clients

The values that typically change for production: the database (a real PostgreSQL, from DATABASE_URL), ddl-auto=validate (Flyway manages the schema — the migrations lesson), log levels (INFO not DEBUG), and error verbosity (no stack traces to clients). Profiles keep these in one place per environment, with no conditional code in the application.

Secrets from the environment, never committed

The security-critical rule (the best-practices lesson), restated for deployment: secrets come from the environment, never the committed files. The database password, the JWT signing key, API keys — these are injected as environment variables (or from a secrets manager) at deploy time and referenced in properties:

spring.datasource.password=${DB_PASSWORD}
dakiya.jwt.secret=${DAKIYA_JWT_SECRET}

The repository holds the references (${DB_PASSWORD}), the deployment environment holds the values. A secret committed to application-prod.properties is a breach the moment it is pushed and lives in git history forever. Set the real values as environment variables in your platform (or a secrets manager); keep the committed files free of them.

The deployment configuration checklist

Before deploying, confirm the production configuration:

  • spring.profiles.active=prod set, so the production profile loads.
  • Database — a real PostgreSQL via DATABASE_URL/credentials from the environment; ddl-auto=validate; Flyway enabled to manage the schema.
  • Secrets — SECRETs and passwords from environment variables / a secrets manager, none committed.
  • Logging — appropriate levels (INFO+), and errors going to logs (the next lesson), not verbose to clients.
  • Error responses — include-stack-trace=never; clients get generic ProblemDetails (the error-handling and security lessons).
  • Server — the port and any reverse-proxy/forward-header settings your platform needs.

Getting these right is the difference between a deploy that runs safely and one that leaks stack traces, tries to auto-mutate the production schema, or fails to find its database. The principle throughout: one build, environment-driven configuration, secrets from the environment, validate not update, and the production profile carrying the differences — so going to production is a configuration change, not a code change.

Check your work

One build, different config. Build once, run the same artifact everywhere with different fed-in values; env vars / CLI args override application.properties (they win). Reproducible, easy rollback.

Profiles. application-{profile}.properties overrides the base for the active profile (SPRING_PROFILES_ACTIVE=prod); production typically changes the database (real Postgres), ddl-auto=validate, log levels (INFO), and error verbosity — no conditional code.

Secrets. From environment variables / a secrets manager, referenced as ${...} in properties, never committed (a committed secret is a breach, forever in history).

The checklist. Prod profile active; real DB + validate + Flyway; secrets externalised; sensible logging; no stack traces to clients; correct server/proxy settings.

Practice

  1. Create application-prod.properties overriding ddl-auto to validate, the datasource to a ${DATABASE_URL}, and root logging to INFO; run with prod active and confirm the values load.
  2. Override a base property with an environment variable and confirm the env var wins.
  3. Reference a secret as ${DAKIYA_JWT_SECRET} and supply it via an environment variable; confirm no secret is in the committed files.
  4. Set server.error.include-stack-trace=never, trigger an error, and confirm no stack trace reaches the client.
  5. Reason about why building one artifact and feeding config (rather than a "prod build") makes rollback trivial.
  6. Walk the deployment configuration checklist against a Dakiya application-prod.properties.

Official documentation

Next: Actuator, health checks and useful logging.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship