CodeGym /Cursos /Docker for Spring /Configuraciones externas: import y location

Configuraciones externas: import y location

Docker for Spring
Nivel 12, Lección 3
Disponible

1. Cuando la búsqueda predeterminada no es suficiente

Si escuchas por primera vez sobre spring.config.import y sus amigos, puedes tener un pequeño pánico: «¿Entonces antes todo estaba mal?». Tranquilo. En la mayoría de los casos, Spring Boot realmente encuentra por sí mismo application.yml y application-{profile}.yml en los lugares esperados, y eso es una buena magia básica que ahorra nervios. Pero a veces no quieres magia, sino un escenario controlado.

Mientras el archivo externo se llamaba application.yml o application-postgres.yml y se encontraba en el lugar habitual junto al arranque, Spring Boot a menudo podía encontrarlo por sí mismo. Pero en cuanto el nombre se vuelve arbitrario — por ejemplo, runtime.yml — o la ruta deja de ser estándar, la búsqueda automática ya no es suficiente. Aquí es donde aparecen spring.config.import, spring.config.location y spring.config.additional-location: no reemplazan a los habituales application*.yml, sino que sirven para gestionar explícitamente archivos no estándar y rutas de búsqueda.

Imagina dos situaciones típicas de un servicio educativo. En la primera, quieres mantener dentro del jar una línea base comprensible que siempre funcione, y junto a ella tener un pequeño archivo runtime.yml que puede existir o no, y si no existe, la aplicación no debe fallar. En la segunda, quieres decir: «Aquí están mis configuraciones, y solo allí. No busques en todos los bolsillos de la chaqueta ni en cada config/». Precisamente estos escenarios nos dan las tres herramientas: spring.config.import, spring.config.location y spring.config.additional-location.

Para no perdernos, tengamos en mente una metáfora sencilla. Por defecto, Spring Boot se comporta como una persona que busca las llaves del apartamento por la ruta habitual: «en el bolsillo, en la mesa, en la mochila». additional-location es cuando añades un lugar más a la ruta: «y también en el cajón del recibidor». location es cuando dices: «para, busca solo en el cajón, en ningún otro sitio». Y import es cuando abres la libreta «dónde guardo las llaves» y dentro escribes: «y además revisa el sobre en la carpeta».

2. spring.config.import: incluir un archivo

spring.config.import se percibe a menudo como «otra forma de incluir una configuración», pero es más útil pensar en él como pegar un documento de configuración a otro. Tomas tu application.yml principal (o archivo específico de perfil) y dices: «Si existe tal archivo externo, léelo también». En el mundo de los contenedores es útil porque no reconstruyes la imagen, solo le das a la aplicación una capa adicional de configuración.

Dónde declarar import

Es muy importante no confundirse «qué se escribe dónde». spring.config.import se puede declarar dentro del archivo de configuración, es decir, directamente en application.yml (que está en src/main/resources y se incluye dentro del jar). Entonces se produce un efecto agradable: la imagen y el jar siempre contienen la línea base, y el archivo externo se convierte en una «capa opcional».

Supongamos que en nuestro servicio ya existe la propiedad app.export-dir (la necesitamos para exportar el catálogo a CSV). Dentro del jar queremos tener un valor predeterminado seguro, por ejemplo /app/exports, y en una máquina de desarrollo concreta queremos cambiarlo a algo como /workspace/catalog-exports.

Entonces el application.yml base podría tener este aspecto:

# src/main/resources/application.yml
spring:
  config:
    import: "optional:file:/config/runtime.yml" # Incluye opcionalmente la capa externa (si el archivo no existe, continúa)

app:
  export-dir: /app/exports # Valor base «desde dentro del jar», predeterminado seguro

Observa dos cosas. Primero, no hacemos obligatorio el archivo externo. Segundo, usamos explícitamente file:/... y una ruta fija dentro del contenedor. Esto no es sobre la mecánica de archivos del contenedor, sino sobre que la aplicación espera encontrar el archivo en una ruta concreta.

Aquí /config/runtime.yml ya no es el habitual application.yml externo que Boot reconoce por su nombre. Hemos elegido deliberadamente un nombre arbitrario y una ruta fija, por lo que el archivo se incluye explícitamente.

Y el archivo externo (que, por ejemplo, puede estar «junto al arranque» o «en el contenedor en la ruta indicada») será muy breve:

# /config/runtime.yml (archivo externo)
app:
  export-dir: /workspace/catalog-exports # Sobrescribe para una máquina/ejecución específica
server:
  port: 9090 # Ejemplo: localmente queremos escuchar en otro puerto

La idea es que el archivo externo contenga solo lo que realmente es «sobre esta máquina / esta ejecución», sin copiar toda la configuración.

optional: y arranque sin archivo

Con el prefijo optional: todo es sencillo, pero las consecuencias son enormes. Sin optional:, Spring Boot considerará que el recurso importado debe existir, y si no está, el inicio puede terminar con un error. Para un proyecto educativo esto casi siempre es desagradable: quieres que la línea base arranque «de fábrica», sin que el estudiante tenga que crear primero archivos secretos en lugares secretos.

Por eso, en nuestro curso una buena disciplina es: si el archivo importado es una «capa opcional», en import casi siempre habrá optional:.

spring:
  config:
    import: "optional:file:/config/runtime.yml"

El significado aquí es muy humano: «Intenta leerlo, pero si no funciona, no te ofendas y arranca con lo que tienes».

Rutas fijas y relativas en import

Ahora un punto sutil, que es la causa más frecuente de «fantasmas» en la configuración. spring.config.import tiene dos estilos de ruta habituales: ubicación fija (fixed location) y relativa (relative, respecto al archivo que declaró el import). En el plan del día lo llamamos «fixed location» y «import-relative location».

La ubicación fija es cuando indicas explícitamente dónde está el archivo, y la ruta no depende de dónde se encuentra el propio archivo de configuración que importa. Por ejemplo:

spring:
  config:
    import: "optional:file:/config/runtime.yml"

Esta es una buena opción para un contenedor: acordaste de antemano que las configuraciones (si es que hay externas) están, por ejemplo, en /config.

La importación relativa es cuando escribes la ruta sin un file:/classpath: explícito y sin una /... absoluta, y entonces Spring Boot la interpreta como «archivo junto a» (relativo al documento de configuración actual). Por ejemplo:

spring:
  config:
    import: "optional:extra/runtime.yml"

¿Para qué sirve? Para un enfoque de «carpetas». Imagina que guardas un conjunto de configuraciones externas en un mismo directorio, por ejemplo:

config/
├── application.yml
└── extra/
    └── runtime.yml

Entonces te resulta conveniente escribir dentro de config/application.yml una importación relativa, y toda la estructura se vuelve portable: copias la carpeta config/ a otra máquina, y las rutas no se rompen.

Es importante no intentar adivinar «cómo exactamente Boot calcula esta ruta» por intuición. El mejor hábito para un principiante es preguntarse siempre: «¿Quiero que la ruta esté ligada a un lugar concreto (fija), o quiero que esté ligada al archivo de configuración actual (relativa)?». Si te respondes a ti mismo, el 80% de la confusión desaparece.

Mini-demo: comprobando la sobreescritura

Para no convertir el tema en teoría sobre YAML, es útil tener en el proyecto un pequeño «sensor» que muestre qué valor se ha obtenido finalmente. El sensor más simple es registrar app.export-dir al inicio.

Supongamos que tenemos propiedades de configuración:

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "app") // Vincula las propiedades app.* con este tipo
public record AppProperties(String exportDir) { } // exportDir <- app.export-dir (enlace relajado)

Supongamos que AppProperties ya está registrado como tipo @ConfigurationProperties para app.*; aquí solo se necesita como contenedor legible de los valores finales.

Y un pequeño registro al inicio:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
class StartupLoggingConfig {
  private static final Logger log = LoggerFactory.getLogger(StartupLoggingConfig.class);

  @Bean
  ApplicationRunner logExportDir(AppProperties props) { // Bean se ejecuta al iniciar la aplicación
    return args -> log.info("app.export-dir = {}", props.exportDir()); // {} — marcador de SLF4J, el valor se toma de la configuración final
  }
}

Si este setup no existe en el proyecto, añade el escaneo o registro habitual de @ConfigurationProperties (@ConfigurationPropertiesScan o @EnableConfigurationProperties).

Luego ya puedes asociar mentalmente: si el runtime.yml externo existe y se ha importado, en los registros verás /workspace/catalog-exports. Si no existe, verás el valor predeterminado /app/exports. Sin misterios, solo comprobamos «qué resultó realmente».

3. spring.config.location: solo las rutas indicadas

spring.config.location es una herramienta que parece ser «otra ruta para incluir configuraciones», pero en significado es mucho más estricta. Le dice a Spring Boot: «Aquí está la lista de lugares donde se encuentra la configuración. Úsalos como la ruta de búsqueda principal». Y si estás acostumbrado a que Boot siempre recoja application.yml del classpath y de algún otro directorio, con location puedes desactivar accidentalmente los valores predeterminados habituales.

Cómo location reemplaza los valores predeterminados

Por «ubicaciones predeterminadas» entendemos el conjunto estándar donde Boot suele buscar application.yml y application-{profile}.yml: dentro del jar (classpath) y junto al arranque (directorio actual, config/, etc.). Cuando usas spring.config.location, es como si dijeras: «No busques la configuración según las reglas estándar. Yo mismo indico dónde está».

De ahí la principal conclusión práctica. spring.config.location es algo potente pero peligroso para un principiante. Es útil cuando realmente quieres controlar el entorno y entiendes lo que haces, pero puede llevar a la situación: «¿Por qué la aplicación de repente ya no ve el application.yml que puse en src/main/resources?». La respuesta será desagradablemente simple: porque tú mismo le pediste a Boot que no mirara allí.

En un proyecto educativo, esos «disparos en el pie» son especialmente molestos porque pierdes la línea base de arranque. Por eso, durante el curso tratamos spring.config.location como una herramienta «para casos concretos», no como una configuración «por si acaso».

Cómo establecer location desde fuera

Ahora una mecánica importante que es fácil de entender a nivel práctico. Para que Spring Boot encuentre la configuración, primero necesita entender dónde buscarla. Por lo tanto, los parámetros «dónde buscar» deben llegar antes que las propias configuraciones. Por eso spring.config.location (y spring.config.additional-location) se especifican como:

- variable de entorno (por ejemplo, SPRING_CONFIG_LOCATION),
- propiedad del sistema JVM (-Dspring.config.location=...),
- argumento de línea de comandos (--spring.config.location=...).

Intentar escribir spring.config.location dentro de application.yml, que Boot aún no ha encontrado, es un bucle lógico: «toma el mapa del cajón que encontrarás usando el mapa».

Un ejemplo con -D sería:

# Propiedad del sistema JVM: establecemos la ruta de búsqueda antes de leer application.yml
java -Dspring.config.location=optional:file:/config/ -jar app.jar

Y como argumento de la aplicación:

# Argumento de la aplicación: el efecto es el mismo, pero se lee como parámetro de arranque
java -jar app.jar --spring.config.location=optional:file:/config/

En un contenedor es lo mismo, pero a menudo es más conveniente usar variables de entorno (recordamos el enlace relajado de la lección anterior):

# Variable de entorno: a menudo la forma más cómoda para contenedores
SPRING_CONFIG_LOCATION=optional:file:/config/

El punto clave aquí no es la sintaxis, sino la idea de ingeniería: estas propiedades se leen muy temprano, por lo que deben estar disponibles antes de que Boot comience a «hojear» tus archivos application.

Cuándo es apropiado location

Para no demonizar la herramienta, vamos a señalar honestamente el escenario en el que es normal. Imagina que ejecutas el mismo jar en un entorno donde no quieres usar en absoluto los valores predeterminados internos (por ejemplo, porque estás haciendo una «configuración rígida» y los valores base en el jar no te sirven). Entonces location permite decir: «Mi configuración está solo en este directorio».

Pero para nuestro servicio educativo «maduro para contenedores», suele ser más adecuada una historia más suave: dejar la línea base dentro del jar y añadir con cuidado sobrescrituras externas por encima. Y aquí a menudo gana spring.config.additional-location, no location.

4. spring.config.additional-location: añadir una ruta de búsqueda

Si spring.config.location es «reescribo las reglas del juego», entonces spring.config.additional-location es «añado otra ruta de búsqueda, pero no descarto el comportamiento estándar». Para un proyecto educativo y para un equipo que busca previsibilidad, suele ser una herramienta más segura y amigable. Ayuda a mantener dos mundos juntos: las configuraciones dentro del jar como línea base y las configuraciones externas como una superposición cuidadosa.

Diferencia entre location y additional-location

En lugar de largas abstracciones, digámoslo así. Si usas additional-location, el application.yml interno (de src/main/resources) sigue existiendo como «esqueleto» de la configuración, y el directorio externo se convierte en una «carpeta adicional donde también pueden estar los archivos application». Esto se parece mucho a la idea de «no copiar todo fuera», sino sacar solo lo necesario.

Por ejemplo, dentro del jar tienes:

- application.yml con configuraciones generales,
- application-postgres.yml, etc.

Y fuera quieres sobrescribir solo server.port y app.export-dir para una máquina concreta. Entonces pones un application.yml externo (breve) en el directorio «adicional» y le dices a Boot: «Mira también aquí».

Esto se establece nuevamente temprano, «desde fuera»:

java -jar app.jar --spring.config.additional-location=optional:file:/config/

o así:

java -Dspring.config.additional-location=optional:file:/config/ -jar app.jar

o con variables de entorno:

SPRING_CONFIG_ADDITIONAL_LOCATION=optional:file:/config/

Ejemplo de estructura de archivos externos

Imaginemos una estructura limpia del directorio externo. No diez archivos «final-prod-really.yml», sino algo muy simple y claro:

/config/
├── application.yml
└── application-postgres.yml

Entonces el application.yml externo podría ser así:

# /config/application.yml (externo)
server:
  port: 9090

app:
  export-dir: /workspace/catalog-exports

Y si quieres sacar fuera los parámetros de postgres (por ejemplo, porque en distintas máquinas hay diferentes credenciales o hosts), puedes tener un application-postgres.yml externo:

# /config/application-postgres.yml (externo)
spring:
  datasource:
    url: jdbc:postgresql://postgres:5432/catalog
    username: catalog
    password: catalog

El sentido es que Boot trabajará con estos archivos igual que con los internos: si el perfil está activado, el archivo de perfil participa; si no, no participa. Y todo esto sin reconstruir la imagen ni copiar el Dockerfile. Nos mantenemos en el paradigma «una imagen, configuraciones diferentes».

optional: para additional-location

Al igual que con import, a menudo quieres que el directorio sea «si existe, úsalo; si no, que no rompa el arranque». Por lo tanto, también aquí es conveniente usar optional:.

--spring.config.additional-location=optional:file:/config/

Entonces el estudiante puede ejecutar el servicio sin ningún directorio externo, y todo funcionará con la configuración base del jar. Y cuando aparezca el directorio externo (como una herramienta consciente, no como un punto de entrada obligatorio), empezará a influir en la configuración.

5. Resumen para elegir el mecanismo

Cuando tres nombres similares te miran fijamente, el cerebro de un principiante suele hacer un intento honesto de «aprender de memoria». Pero en el desarrollo real no gana la memoria, sino la pregunta correcta. En nuestro caso, la pregunta es: «¿Quiero añadir una pequeña capa sobre la configuración existente, o quiero reemplazar completamente la ruta de búsqueda de la configuración?»

Para simplificar la elección, aquí tienes una tabla que puedes tener en mente (y no dudes en volver a ella):

Mecanismo Qué le dices a Boot Cuándo suele ser adecuado Qué se puede romper fácilmente
spring.config.import «Desde esta configuración, lee también este archivo» Cuando necesitas un archivo adicional concreto (a menudo runtime.yml) y quieres gestionarlo directamente desde application.yml Si olvidas optional: y el archivo no existe, el inicio puede fallar
spring.config .additional-location «Busca archivos application también en este directorio, pero deja los valores predeterminados» Cuando necesitas un application.yml/application-{profile}.yml externo como superposición a la línea base dentro del jar Si esperas que sea un «reemplazo», pero es un «añadido», puedes sorprenderte con la precedencia final
spring.config .location «Busca la configuración solo por esta ruta (en esencia, reescribe la ruta de búsqueda)» Cuando quieres desactivar conscientemente las ubicaciones predeterminadas y vivir solo con la configuración externa Es muy fácil desactivar la configuración del classpath y perder la línea base

En resumen, al estilo «cómo se lo explicaría a un amigo»: import es sobre «vincular un archivo a otro», additional-location es sobre «añadir otra carpeta de búsqueda», location es sobre «reemplazar toda la lista de carpetas de búsqueda».

6. Errores típicos

Error nº1: usar spring.config.location cuando en realidad solo necesitabas «añadir otra configuración».
Esto es clásico. Alguien quería «meter un archivo externo», pone spring.config.location, y de repente el application.yml base dentro del jar deja de participar. Como resultado, algunas propiedades «desaparecen» y parece que la aplicación se ha roto. En la práctica, la mayoría de las veces necesitas spring.config.additional-location o un spring.config.import cuidadoso.

Error nº2: olvidar optional: y convertir el archivo/directorio externo en un bloqueante del arranque.
En un proyecto educativo y en la mayoría de escenarios locales, el archivo externo es una «superposición útil», no una parte obligatoria del inicio. Si olvidas optional:, la aplicación puede fallar solo porque el archivo no está en una máquina concreta. Esto es especialmente molesto en equipo: a uno le funciona todo, a otro «falla al arrancar» y empieza el misterio.

Error nº3: intentar establecer spring.config.location dentro de application.yml.
Esto es un bucle lógico, no una configuración. Los parámetros spring.config.location y spring.config.additional-location se leen muy temprano, antes de que Boot haya procesado tus archivos de configuración. Por lo tanto, se establecen mediante variables de entorno, -D o argumentos de arranque. Si pones esto en un archivo que debe ser encontrado mediante esas mismas reglas, obtienes «busco un mapa para encontrar un mapa».

Error nº4: confundir ubicación fija e importación relativa, y luego sorprenderse de que «no encuentra el archivo».
Si escribiste spring.config.import: optional:extra/runtime.yml, elegiste conscientemente una ruta relativa. Depende de dónde esté el archivo que declaró el import. Si moviste el archivo o cambiaste la estructura de directorios, la ruta relativa también cambiará. Para un contenedor, suele ser más sencillo usar ubicación fija (file:/config/runtime.yml) para no tener que pensar «relativo a qué».

Error nº5: importar demasiados archivos y construir un laberinto de configuración.
spring.config.import es una herramienta útil, pero no debe convertirse en una novela «Guerra y paz» de 12 capítulos. Cuando hay muchas importaciones, el principiante pierde el objetivo principal del día: la previsibilidad. Durante el curso, es mejor mantener la cadena corta: un archivo runtime externo o un directorio adicional, y ya está.

1
Tarea
Docker for Spring, nivel 12, lección 3
Bloqueada
`spring.config.import` con archivo externo opcional
`spring.config.import` con archivo externo opcional
1
Tarea
Docker for Spring, nivel 12, lección 3
Bloqueada
Diferencia entre `additional-location` y `location`
Diferencia entre `additional-location` y `location`
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION