Centralized Configuration with Spring Cloud Config Server
Managing configuration for 10 services across 3 environments means 30 separate config files. Spring Cloud Config Server centralizes all of them — one place to change a database URL, one place to rotate secrets, and services pick up changes without redeployment.
Config Server Setup
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
@SpringBootApplication
@EnableConfigServer
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/devops-monk/config-repo
default-label: main
search-paths: '{application}' # look for config in a directory named after the service
clone-on-start: true
# For private repos:
username: ${GIT_USERNAME}
password: ${GIT_TOKEN}
The Config Server reads configuration files from a Git repository and serves them over HTTP.
Config Repository Structure
config-repo/
├── application.yml # shared across all services
├── order-service/
│ ├── application.yml # order-service defaults
│ ├── application-dev.yml # dev profile
│ └── application-prod.yml # prod profile
├── payment-service/
│ ├── application.yml
│ └── application-prod.yml
└── customer-service/
└── application.yml
File naming convention: {application}/{application}-{profile}.yml
Resolved configuration order (last wins):
application.yml(shared defaults){service-name}/application.yml(service defaults){service-name}/application-{profile}.yml(profile-specific)
Config Client Setup
Each service adds:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
# bootstrap.yml (loaded before application.yml — needed for Config Server URL)
# OR set via application.yml with spring.config.import:
spring:
application:
name: order-service # ← determines which config files are fetched
config:
import: optional:configserver:http://localhost:8888
cloud:
config:
fail-fast: true # crash if config server unreachable
retry:
max-attempts: 6
initial-interval: 1000
multiplier: 1.5
max-interval: 2000
On startup, order-service fetches its configuration from http://localhost:8888/order-service/default (or /order-service/prod for the prod profile) and merges it with local application.yml.
Config Server Endpoints
# Get config for order-service, default profile
GET http://localhost:8888/order-service/default
# Get config for order-service, prod profile
GET http://localhost:8888/order-service/prod
# Get config for order-service, prod profile, main branch
GET http://localhost:8888/order-service/prod/main
# Get a specific file
GET http://localhost:8888/order-service/default/main/application.yml
Response includes all properties resolved for that service/profile combination.
Encrypting Secrets
Never store secrets in plaintext in Git. Config Server can encrypt/decrypt values:
# config-server application.yml
encrypt:
key: ${ENCRYPTION_KEY} # symmetric key (AES)
# Or use RSA key pair:
# key-store:
# location: classpath:config-server.jks
# password: ${KEYSTORE_PASSWORD}
# alias: configserver
Encrypt a value:
curl -X POST http://localhost:8888/encrypt -d 'my-secret-password'
# Returns: AQA+9Kd5X8mGhY...
Store in config repo:
# order-service/application-prod.yml
spring:
datasource:
password: '{cipher}AQA+9Kd5X8mGhY...' # encrypted — decrypted at fetch time
The Config Server decrypts {cipher}... values before serving them to clients. The secret is never exposed in Git.
Dynamic Refresh with @RefreshScope
When config changes in Git, services can pick it up without restarting:
<!-- client service -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-kafka</artifactId>
</dependency>
Mark beans that should reload on config change:
@Service
@RefreshScope // bean is re-created when /actuator/refresh is called
@RequiredArgsConstructor
public class PaymentGatewayService {
@Value("${payment.gateway.api-key}")
private String apiKey; // refreshed when config changes
@Value("${payment.gateway.url}")
private String gatewayUrl;
}
Trigger refresh on a specific service:
POST http://order-service:8080/actuator/refresh
# Refreshes @RefreshScope beans in this instance
Or broadcast refresh to all instances via Spring Cloud Bus (Kafka):
POST http://config-server:8888/actuator/busrefresh
# Broadcasts refresh event to all services on the bus
All services receive the refresh event via Kafka and reload their @RefreshScope beans — no restarts needed.
Automatic Refresh with Webhooks
Configure Git to send a webhook to Config Server on every push:
GitHub/GitLab webhook:
Push → POST http://config-server:8888/actuator/busrefresh
Config Server → publishes refresh event to Kafka
Kafka → all services consume event → refresh @RefreshScope beans
Configuration changes are live within seconds of a Git push.
Spring Cloud Vault (Alternative for Secrets)
For production secret management, use HashiCorp Vault instead of Config Server’s encryption:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
spring:
config:
import: vault://
cloud:
vault:
uri: https://vault.devopsmonk.com
authentication: KUBERNETES # use Kubernetes service account token
kubernetes:
role: order-service
kv:
enabled: true
default-context: order-service
Vault provides: secret rotation, dynamic credentials (DB passwords that expire), audit logs, fine-grained access control. For production microservices, Vault is the better choice for secrets.
Config Server with Kubernetes ConfigMaps
In Kubernetes, you can use ConfigMaps and Secrets as the config source instead of Git:
spring:
config:
import: kubernetes:
cloud:
kubernetes:
config:
enabled: true
sources:
- name: order-service-config
namespace: default
secrets:
enabled: true
sources:
- name: order-service-secrets
namespace: default
Kubernetes ConfigMaps and Secrets mount as Spring configuration — no Config Server process needed.
What You’ve Learned
- Config Server serves configuration from a Git repository over HTTP
- Services fetch config on startup by declaring
spring.config.import: configserver:...and theirspring.application.name - Encrypt secrets with
{cipher}...— Config Server decrypts at fetch time, never exposes plaintext in Git @RefreshScopebeans reload whenPOST /actuator/refreshis called- Spring Cloud Bus broadcasts refresh to all instances via Kafka — no per-instance restarts
- For production secrets, Vault provides rotation, dynamic credentials, and audit logs
- On Kubernetes, use ConfigMaps/Secrets as native config sources instead of a Config Server process
Next: Article 50 — Resilience Patterns with Resilience4j — circuit breakers, retries, rate limiters, and bulkheads for resilient microservices.