Contents
- Architecture Overview
- Config Server Setup
- Git Repository Structure
- Client Service Setup
- Profile-Specific & Label-Based Config
- Live Refresh with @RefreshScope
- Encrypting Sensitive Properties
- Securing the Config Server
The Config Server acts as an HTTP endpoint that serves properties files pulled from a Git repository. Each client service fetches its configuration by providing its application name, active profile, and label (branch/tag/commit).
- Config Server — a Spring Boot app with @EnableConfigServer; reads from Git, filesystem, Vault, or JDBC
- Config Client — any Spring Boot service with spring-cloud-starter-config; fetches properties at startup
- Config repo — a plain Git repo with YAML/properties files named {app}-{profile}.yml
The Config Server is stateless — it simply proxies properties from the backing Git repo. Scale it horizontally behind a load balancer and point all services at the same URL. The Git clone is cached locally per server instance.
<!-- pom.xml — Config Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
@SpringBootApplication
@EnableConfigServer // single annotation turns on the Config Server
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
# application.yml — Config Server
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/my-org/config-repo # or git@github.com:...
default-label: main # branch to use
clone-on-start: true # eager clone at startup
search-paths: # subdirectories to search
- '{application}' # per-app subfolder
# For private repos:
username: ${GIT_USERNAME}
password: ${GIT_TOKEN}
# Or SSH key:
# private-key: ${GIT_PRIVATE_KEY}
management:
endpoints:
web:
exposure:
include: health,info,refresh,bus-refresh
Name files using the pattern {application}-{profile}.yml. Files are resolved in priority order: most specific first, then falling back to more generic.
config-repo/
├── application.yml # shared defaults for ALL services
├── application-prod.yml # shared prod overrides for ALL services
│
├── order-service.yml # order-service defaults (all profiles)
├── order-service-dev.yml # order-service dev overrides
├── order-service-prod.yml # order-service prod overrides
│
├── payment-service.yml
├── payment-service-prod.yml
│
└── inventory-service.yml
# application.yml — shared defaults for all services
logging:
level:
root: INFO
management:
endpoints:
web:
exposure:
include: health,info
---
# order-service-prod.yml
spring:
datasource:
url: jdbc:postgresql://prod-db.internal:5432/orders
username: orders_app
password: '{cipher}AQABAAEAgBJ...' # encrypted value
app:
order:
timeout-seconds: 30
max-retries: 3
Verify the server is serving config correctly by hitting the endpoint directly:
# GET /{application}/{profile}/{label}
curl http://localhost:8888/order-service/prod/main
curl http://localhost:8888/order-service/dev
# Response includes all resolved property sources in priority order
<!-- pom.xml — Config Client (each microservice) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
# application.yml — Config Client
spring:
application:
name: order-service # determines which files are fetched from config repo
config:
import: "optional:configserver:http://localhost:8888"
# "optional:" means app starts even if server is down
profiles:
active: dev
cloud:
config:
fail-fast: true # fail startup if config server unreachable (prod)
retry:
max-attempts: 6
initial-interval: 1000
multiplier: 1.5
max-interval: 2000
// Properties fetched from Config Server are injected normally
@Service
public class OrderService {
@Value("${app.order.timeout-seconds:10}")
private int timeoutSeconds;
@Value("${app.order.max-retries:3}")
private int maxRetries;
}
// Or use @ConfigurationProperties for type-safe binding
@ConfigurationProperties(prefix = "app.order")
@Component
public class OrderProperties {
private int timeoutSeconds = 10;
private int maxRetries = 3;
// getters/setters
}
# Priority order (highest to lowest) for order-service with profile=prod, label=main:
# 1. order-service-prod.yml (app + profile specific)
# 2. order-service.yml (app specific, all profiles)
# 3. application-prod.yml (shared prod overrides)
# 4. application.yml (shared defaults)
# Labels — serve config from a specific branch or tag
spring:
cloud:
config:
label: release/v2.1 # fetch from this Git branch/tag
# Config Server also serves native file format (e.g., for feature branches)
# Composite backend — Git for prod, filesystem for local dev
spring:
cloud:
config:
server:
composite:
- type: git
uri: https://github.com/my-org/config-repo
- type: native
search-locations: file:./local-config
Without restart, services can reload config by calling the /actuator/refresh endpoint (or using Spring Cloud Bus for fan-out). Beans annotated with @RefreshScope are re-created on refresh.
// Mark the bean as refreshable
@RefreshScope
@Service
public class FeatureFlagService {
@Value("${feature.new-checkout-enabled:false}")
private boolean newCheckoutEnabled;
public boolean isNewCheckoutEnabled() {
return newCheckoutEnabled; // re-injected after refresh
}
}
# Trigger a refresh on one service instance
curl -X POST http://order-service:8080/actuator/refresh
# Fan-out refresh to ALL instances via Spring Cloud Bus (Kafka or RabbitMQ)
# Add: spring-cloud-starter-bus-kafka to all services
curl -X POST http://config-server:8888/actuator/bus-refresh
# Or refresh only services with a specific destination
curl -X POST "http://config-server:8888/actuator/bus-refresh/order-service:**"
@RefreshScope does NOT work with @ConfigurationProperties beans directly — those need @RefreshScope on the class OR you can use Spring Cloud's auto-rebinding by adding spring-cloud-context which auto-refreshes @ConfigurationProperties beans.
Config Server has built-in encryption/decryption. Store ciphered values in Git — Config Server decrypts them before serving to clients.
# Config Server application.yml — set the encryption key
encrypt:
key: my-symmetric-key # or use asymmetric RSA key pair
# For RSA (more secure):
# key-store:
# location: classpath:keystore.jks
# password: ${KEYSTORE_PASSWORD}
# alias: config-server-key
# Encrypt a value
curl -X POST http://localhost:8888/encrypt -d 'my-secret-password'
# Returns: AQABAAEAgBJ3xFq7...
# Decrypt (for verification only — clients never call this)
curl -X POST http://localhost:8888/decrypt -d 'AQABAAEAgBJ3xFq7...'
# Returns: my-secret-password
# Store in Git config file with {cipher} prefix
spring:
datasource:
password: '{cipher}AQABAAEAgBJ3xFq7...'
# Config Server decrypts this before sending to the client
# Client receives the plaintext value
For production use, prefer HashiCorp Vault as the Config Server backend instead of Git-based encryption. Spring Cloud Vault integrates natively and provides dynamic secrets, lease rotation, and fine-grained access policies.
# Config Server — add Spring Security HTTP Basic auth
# pom.xml: spring-boot-starter-security
spring:
security:
user:
name: config-admin
password: ${CONFIG_SERVER_PASSWORD}
# Config Client — provide credentials
spring:
cloud:
config:
uri: http://localhost:8888
username: config-admin
password: ${CONFIG_SERVER_PASSWORD}
# Production pattern — put Config Server behind service mesh or API gateway
# and use mutual TLS or OAuth2 token-based auth instead of HTTP Basic
spring:
cloud:
config:
tls:
enabled: true
key-store: classpath:client-keystore.p12
key-store-password: ${TLS_PASSWORD}
trust-store: classpath:truststore.p12
trust-store-password: ${TLS_TRUST_PASSWORD}