Profiles y configuración por entorno

Avanzado
Spring Boot
Spring Boot
Actualizado: 07/05/2026

Diagrama: tutorial-spring-boot-profiles-configuracion-entorno

El problema: una aplicación, varios entornos

Toda aplicación Spring Boot real se ejecuta en al menos tres entornos: el portátil del desarrollador (dev), el cluster de pruebas o staging (staging) y producción (prod). Cada uno necesita configuración distinta:

  • dev: H2 in-memory o PostgreSQL local en Docker, SMTP fake, logging en DEBUG, endpoints de Actuator todos abiertos, sin TLS.
  • staging: PostgreSQL del servidor staging, SMTP de Mailtrap, logging en INFO, métricas a Prometheus interno, datos sintéticos.
  • prod: PostgreSQL en HA con réplica, SMTP corporativo, logging en WARN con JSON estructurado, Actuator restringido, TLS, secretos de Vault.

Hardcodear estas diferencias en código o pasarlas como argumentos en cada despliegue es frágil y poco mantenible. Spring Boot resuelve esto con profiles y un sistema de propiedades externalizadas en capas.

Anatomía de un proyecto multi-entorno

La estructura habitual en src/main/resources/:

application.yml              # Propiedades comunes a todos los entornos
application-dev.yml          # Solo cuando el perfil "dev" está activo
application-staging.yml      # Solo cuando "staging" está activo
application-prod.yml         # Solo cuando "prod" está activo
application-test.yml         # Solo cuando "test" está activo (tests JUnit)

El archivo base application.yml contiene lo común y referencias a placeholders que se rellenan según el perfil:

# application.yml
spring:
  application:
    name: empleados-api
  jpa:
    open-in-view: false
    hibernate:
      ddl-auto: validate

server:
  port: 8080
  shutdown: graceful

management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus

logging:
  level:
    root: INFO

application-dev.yml sobreescribe lo que cambia en dev:

# application-dev.yml
spring:
  datasource:
    url: jdbc:h2:mem:empleados;DB_CLOSE_DELAY=-1
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
  h2:
    console:
      enabled: true

logging:
  level:
    com.empresa.empleados: DEBUG
    org.springframework.web: DEBUG

application-prod.yml define lo que cambia en producción, con secretos referenciados por variables de entorno:

# application-prod.yml
spring:
  datasource:
    url: jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=require
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 20
      connection-timeout: 30000

management:
  endpoints:
    web:
      exposure:
        include: health, info, prometheus
  endpoint:
    health:
      show-details: never

logging:
  level:
    root: WARN
    com.empresa.empleados: INFO
  pattern:
    console: '{"timestamp":"%d{yyyy-MM-dd HH:mm:ss.SSS}","level":"%level","logger":"%logger","message":"%message"}%n'

Los placeholders ${DB_HOST} se resuelven desde variables de entorno del contenedor o pod. Producción nunca contiene secretos en el archivo .yml que se commitea; solo placeholders.

Activar un perfil

Por argumento de línea de comandos

java -jar empleados-api.jar --spring.profiles.active=prod

Por variable de entorno

SPRING_PROFILES_ACTIVE=prod java -jar empleados-api.jar

En Docker:

ENV SPRING_PROFILES_ACTIVE=prod

En Kubernetes:

env:
  - name: SPRING_PROFILES_ACTIVE
    value: "prod"

En tiempo de test

@SpringBootTest
@ActiveProfiles("test")
class EmpleadoIntegrationTest {
    // ...
}

Por defecto en application.yml

spring:
  profiles:
    active: dev

Anti-patrón: poner spring.profiles.active=prod por defecto en application.yml. Si el archivo se commitea con ese valor, una ejecución accidental sin variable de entorno arrancará en modo prod en local. Lo idiomático es default a dev y forzar el perfil en cada deploy.

Múltiples perfiles activos a la vez

Spring Boot admite varios perfiles activos. Útil para separar dimensiones (entorno vs feature flag):

--spring.profiles.active=prod,kafka-enabled,observability-full

Cada perfil carga su archivo application-{nombre}.yml. Si dos perfiles definen la misma propiedad, gana el último listado. Esto permite composiciones flexibles, pero hay que conocer la regla para evitar sorpresas.

Profile groups (Spring Boot 2.4+)

Cuando una arquitectura tiene perfiles compuestos, los profile groups evitan tener que listar cada uno:

# application.yml
spring:
  profiles:
    group:
      production: prod, kafka-enabled, observability-full, audit-enabled
      development: dev, kafka-disabled
      staging: staging, kafka-enabled, observability-light

Con esto, activar --spring.profiles.active=production activa los cuatro perfiles del grupo. Útil para no contaminar la línea de comandos con listas largas y evitar olvidos.

@Profile sobre beans

A veces hace falta que un bean exista solo en ciertos perfiles. La anotación @Profile lo controla declarativamente:

@Configuration
public class EmailConfig {

    @Bean
    @Profile("dev | test")
    public EmailService emailServiceFake() {
        return new FakeEmailService(); // captura emails para inspeccionar
    }

    @Bean
    @Profile("staging")
    public EmailService emailServiceMailtrap() {
        return new SmtpEmailService("smtp.mailtrap.io", 587);
    }

    @Bean
    @Profile("prod")
    public EmailService emailServiceCorporativo(
            @Value("${corp.smtp.host}") String host,
            @Value("${corp.smtp.port}") int port) {
        return new SmtpEmailService(host, port);
    }
}

La sintaxis dev | test significa "dev O test". También admite & (AND) y ! (NOT):

  • @Profile("prod & !maintenance") — prod y NO maintenance
  • @Profile("!dev & !test") — ni dev ni test (cualquier otro)

Spring Boot 4 mantiene esta sintaxis. Es el método declarativo más limpio para activar diferentes implementaciones según entorno.

@Profile sobre clases @Configuration enteras

Para activar un conjunto coherente de beans:

@Configuration
@Profile("prod")
public class ProductionSecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
            .requiresChannel(ch -> ch.anyRequest().requiresSecure()) // forza HTTPS
            // ...
            .build();
    }
}

En perfiles que no sean prod, esa clase no se carga y sus beans no existen. Útil para evitar configuraciones de producción en local.

@ConditionalOn... como alternativa más fina

@Profile es lo más usado, pero Spring tiene anotaciones condicionales más finas:

  • @ConditionalOnProperty(name = "feature.x.enabled", havingValue = "true") — activa el bean solo si una propiedad concreta es true. Más granular que @Profile.
  • @ConditionalOnMissingBean — solo si no hay otro bean del mismo tipo registrado.
  • @ConditionalOnClass(name = "com.example.Foo") — solo si una clase está en el classpath.

Para feature flags, @ConditionalOnProperty es preferible a @Profile porque desacopla la activación del entorno:

@Bean
@ConditionalOnProperty(name = "feature.export-csv.enabled", havingValue = "true")
public CsvExporter csvExporter() {
    return new CsvExporter();
}
# application.yml
feature:
  export-csv:
    enabled: false   # apagado por defecto

# application-prod.yml
feature:
  export-csv:
    enabled: true    # encendido en prod

Precedencia entre fuentes de propiedades

Spring Boot resuelve cada propiedad consultando fuentes en este orden (de mayor a menor prioridad):

flowchart TD
    A[Argumentos linea comandos<br/>--spring.profiles.active=prod] --> R[Valor final]
    B[Variables de entorno<br/>SPRING_DATASOURCE_URL] --> R
    C[application-prod.yml] --> R
    D[application.yml] --> R
    E[Defaults de Spring Boot] --> R
    A -.gana sobre.-> B
    B -.gana sobre.-> C
    C -.gana sobre.-> D
    D -.gana sobre.-> E

Esto significa:

  1. Una variable de entorno siempre sobreescribe lo que pone el archivo .yml.
  2. Un archivo application-prod.yml sobreescribe application.yml solo si el perfil prod está activo.
  3. Un argumento --spring.x.y=z gana sobre todo lo demás.

Implicación práctica: la única forma fiable de cambiar configuración en producción sin redeployar es vía variables de entorno. Forzar comportamientos con argumentos en línea de comandos es excepcional.

spring.config.import: traer configuración externa

A partir de Spring Boot 2.4 (y reforzado en 3.x y 4.x), spring.config.import permite cargar fuentes externas sin escribir código:

# application-prod.yml
spring:
  config:
    import:
      - "vault://"                              # secretos desde HashiCorp Vault
      - "configserver:http://config-server"      # Spring Cloud Config Server
      - "optional:configtree:/etc/secrets/"      # archivos del filesystem (k8s secrets)
      - "optional:file:/etc/empleados-api/"      # archivos locales sobreescribibles

El prefijo optional: evita que la aplicación falle si la fuente no está disponible (útil en local cuando no hay Vault levantado).

configtree: lee cada archivo de un directorio como una propiedad: /etc/secrets/db.password → propiedad db.password. Es el patrón estándar para Kubernetes Secrets montados como volume.

@ConfigurationProperties + Profiles

Combinando profiles con @ConfigurationProperties (lección siguiente) se obtiene un patrón potente: una clase Java tipada que cambia automáticamente entre entornos.

@ConfigurationProperties(prefix = "app.email")
@Validated
public record EmailProperties(
    @NotBlank String host,
    @Min(1) @Max(65535) int port,
    String username,
    String password,
    boolean tls
) {}
# application-dev.yml
app:
  email:
    host: localhost
    port: 1025
    tls: false

# application-prod.yml
app:
  email:
    host: ${SMTP_HOST}
    port: ${SMTP_PORT:587}
    username: ${SMTP_USERNAME}
    password: ${SMTP_PASSWORD}
    tls: true

El servicio recibe la configuración tipada por inyección:

@Service
public class EmailSenderImpl implements EmailService {

    private final EmailProperties props;

    public EmailSenderImpl(EmailProperties props) {
        this.props = props;
    }

    @Override
    public void enviar(String destinatario, String asunto, String cuerpo) {
        var session = Session.getInstance(propsToJavaMail(props));
        // ...
    }
}

La validación con @Validated + Bean Validation impide que la aplicación arranque si una propiedad obligatoria falta. Ese fallo en arranque es siempre preferible a un NullPointerException en runtime tres horas después del deploy.

Buenas prácticas en proyectos enterprise

Ningún secreto en archivos commiteados

Los archivos application-*.yml se commitean. Los secretos no. La regla es: en application-prod.yml solo placeholders (${DB_PASSWORD}), nunca el valor real. Los valores reales vienen de variables de entorno, Vault, Consul o Kubernetes Secrets.

Un perfil por entorno + perfiles transversales

Estructura recomendada:

  • Perfiles de entorno: dev, staging, prod, test. Mutuamente exclusivos.
  • Perfiles transversales: kafka-enabled, audit-enabled, observability-full. Combinables con cualquier entorno.
  • Profile groups: development, production, staging que agrupan los anteriores.

Default sensato

application.yml debe ser ejecutable por defecto en local sin variables de entorno. El perfil por defecto es dev y carga H2 in-memory o un docker-compose local.

Configuración tipada y validada

Cualquier propiedad usada en más de un sitio debe estar tipada con @ConfigurationProperties y validada con Bean Validation. La lección siguiente desarrolla este patrón en detalle.

Endpoints de Actuator restringidos en prod

En dev management.endpoints.web.exposure.include: '*' está bien. En prod solo health, info, prometheus con detalle restringido (management.endpoint.health.show-details: never).

Aplicar profiles bien hechos elimina la mayor parte de los incidentes de "funciona en mi máquina pero no en prod". Y deja la base para añadir luego configuración tipada, manejo de errores avanzado y observabilidad sin tocar código de negocio.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Spring Boot es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Spring Boot

Explora más contenido relacionado con Spring Boot y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Configurar perfiles dev, staging y prod con application-{env}.yml. Aplicar @Profile a beans, configuraciones y tests. Componer perfiles con profile groups. Importar configuración externa con spring.config.import. Comprender la precedencia entre command-line args, env vars, application.yml y profile-specific files.