A lightweight, Spring Cloud Config-compatible configuration server built in Go.
Config Server Go is a centralized configuration management microservice that serves versioned YAML configuration files over HTTP. It automatically flattens YAML into dot-notation properties, making it a drop-in backend for applications that consume Spring Cloud Config-style configuration. Manage multiple configuration versions dynamically — list, switch, add, and delete — without restarting the server.
- Spring Cloud Config-compatible responses — returns
propertySourcesJSON that Spring Boot clients understand natively - YAML-to-properties flattening — automatically converts nested YAML into dot-notation key-value pairs
- Multi-version configuration — maintain multiple config versions side-by-side and switch between them at runtime
- Raw file serving — download any config file directly from the active version
- Hot-reload — configuration changes are detected via
fsnotifyand applied without restart - Graceful shutdown — handles
SIGINT/SIGTERMcleanly, draining in-flight requests - Path traversal protection — sanitizes file paths to prevent directory escape attacks
- Docker-ready — multi-stage build produces a minimal Alpine image with health checks
Download the latest binary from the Releases page:
| Platform | Binary |
|---|---|
| Linux (amd64) | config-server-linux-amd64 |
| Linux (arm64) | config-server-linux-arm64 |
| macOS (Apple Silicon) | config-server-darwin-arm64 |
| macOS (Intel) | config-server-darwin-amd64 |
| Windows | config-server-windows-amd64.exe |
chmod +x config-server-*
./config-server-linux-amd64docker pull ghcr.io/roniel-rhack/config-server-go:1docker run -p 8888:8888 ghcr.io/roniel-rhack/config-server-go:1cd src
go run config_server.goThe server starts on http://localhost:8888 by default.
curl http://localhost:8888/health
# okGET /health
Returns ok with a 200 status. Used by Docker HEALTHCHECK and load balancers.
GET /v1/versions — List all configuration versions
Returns the current active version and all available versions, including the files present in each version directory.
Response 200 OK
{
"current": {
"version": "v1.0.0",
"folder": "/opt/packages/config-server/configs/v1.0.0/",
"files": ["application-dev.yaml", "application-prod.yaml"]
},
"available": [
{
"version": "v1.0.0",
"folder": "/opt/packages/config-server/configs/v1.0.0/",
"files": ["application-dev.yaml", "application-prod.yaml"]
},
{
"version": "v2.0.0",
"folder": "/opt/packages/config-server/configs/v2.0.0/",
"files": ["application-dev.yaml"]
}
]
}PUT /v1/versions/set — Switch the active configuration version
Request body
{
"version": "v2.0.0"
}Response 200 OK
{
"success": "Version set"
}Response 200 OK (already active)
{
"success": "Version already set"
}Error responses
| Status | Body | Condition |
|---|---|---|
400 |
{"error": "Invalid request"} |
Malformed or unparseable JSON body |
400 |
{"error": "Invalid version"} |
version field is empty |
400 |
{"error": "Version not available"} |
Version does not exist in AVAILABLE_VERSIONS |
500 |
{"error": "Error saving config"} |
Failed to persist the change to disk |
POST /v1/versions/add — Create a new configuration version
Creates a new version directory and registers it in the available versions list. Spaces in the version name are replaced with underscores.
Request body
{
"version": "v3.0.0"
}Response 200 OK
{
"success": "Version added"
}Error responses
| Status | Body | Condition |
|---|---|---|
400 |
{"error": "Invalid request"} |
Malformed or unparseable JSON body |
400 |
{"error": "Invalid version"} |
version field is empty |
400 |
{"error": "Version already available"} |
Version already exists |
500 |
{"error": "Error creating folder"} |
Failed to create version directory |
500 |
{"error": "Error saving config"} |
Failed to persist the change to disk |
DELETE /v1/versions/:version/delete — Remove a configuration version
Deletes the version directory and removes it from the available versions list. The currently active version cannot be deleted.
Example
DELETE /v1/versions/v2.0.0/delete
Response 200 OK
{
"success": "Version deleted"
}Error responses
| Status | Body | Condition |
|---|---|---|
400 |
{"error": "Invalid version"} |
Version parameter is empty |
400 |
{"error": "Cannot delete current version"} |
Attempted to delete the active version |
400 |
{"error": "Version not available"} |
Version does not exist in AVAILABLE_VERSIONS |
500 |
{"error": "Error deleting folder"} |
Failed to remove version directory |
500 |
{"error": "Error saving config"} |
Failed to persist the change to disk |
GET /:filename — Download a raw configuration file
Serves the raw contents of a file from the currently active version directory. Useful for downloading YAML files directly.
Example
GET /application-dev.yaml
Response 200 OK — raw file contents with appropriate content type.
Error responses
| Status | Body | Condition |
|---|---|---|
400 |
{"error": "Invalid filename"} |
Path traversal attempt detected |
404 |
{"error": "File not found"} |
File does not exist in the active version |
500 |
{"error": "Error reading file"} |
I/O error while reading the file |
GET /:appName/:profile — Get config as flattened properties (Spring Cloud Config format)
Returns configuration from a YAML file named {appName}-{profile}.yaml in the active version, flattened into dot-notation key-value pairs. The response format is compatible with Spring Cloud Config clients.
Example
GET /application/dev
Reads from: <CONFIG_FOLDER>/<CURRENT_VERSION>/application-dev.yaml
Response 200 OK
{
"name": "application",
"propertySources": [
{
"name": "application",
"source": {
"server.port": "8080",
"spring.datasource.url": "jdbc:mysql://localhost:3306/mydb",
"spring.datasource.username": "root",
"logging.level.root": "INFO"
}
}
]
}The server reads its configuration from config.yaml, searched in the following order:
/opt/packages/config-server/- Current working directory
If no file is found, defaults are used and a new config.yaml is written automatically.
| Key | Type | Default | Description |
|---|---|---|---|
SERVER.PORT |
int |
8888 |
Port the HTTP server listens on |
CONFIG_FOLDER |
string |
/opt/packages/config-server/configs/ |
Root directory containing version subdirectories |
CONFIG_FILE |
string |
/opt/packages/config-server/config.yaml |
Path where the config file is written/persisted |
CURRENT_VERSION |
string |
(empty) | The currently active configuration version |
AVAILABLE_VERSIONS |
[]string |
[] |
List of registered version names |
SERVER:
PORT: 8888
CONFIG_FOLDER: /opt/packages/config-server/configs/
CONFIG_FILE: /opt/packages/config-server/config.yaml
CURRENT_VERSION: v1.0.0
AVAILABLE_VERSIONS:
- v1.0.0
- v2.0.0src/
├── config_server.go # Entry point, route definitions, graceful shutdown
├── config/
│ └── config.go # Viper setup, defaults, file watching (fsnotify)
├── custom_logguer/ # Styled terminal logging with charmbracelet/log
├── models/
│ ├── available_versions.go # AvailableVersions and Version structs
│ ├── property_source.go # Config and PropertySources response models
│ ├── set_version.go # SetVersion request model
│ ├── web_200.go # WebSuccess response model
│ └── web_error.go # WebError response model
├── pkg/ # YAML-to-properties parsing and transformation
├── services/
│ ├── properties.go # GetConfigFile, GetConfig handlers
│ └── versions.go # GetVersions, SetVersion, AddVersion, DeleteVersion
├── utils/ # Path resolution and version helpers
└── versions/ # Version loader and file enumeration
The Dockerfile uses a two-stage build:
- Builder stage (
golang:1.25) — downloads dependencies, compiles a statically-linked binary withCGO_ENABLED=0 - Production stage (
alpine:3) — copies only the binary, runs as non-rootappuser(UID 1000)
The final image is minimal and contains no Go toolchain.
The container includes a built-in health check that polls /health every 30 seconds:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget -qO- http://localhost:8888/health || exit 1docker compose up --buildThe default docker-compose.yml maps port 8888:8888.
docker run -d \
-p 8888:8888 \
-v ./config.yaml:/opt/packages/config-server/config.yaml \
-v ./configs:/opt/packages/config-server/configs \
ghcr.io/roniel-rhack/config-server-go:1All commands are run from the src/ directory.
# Install dependencies
go mod download
# Run the server
go run config_server.go
# Build the binary
go build -v ./...
# Run static analysis
go vet ./...
# Run tests with race detection
go test -race -coverprofile=coverage.out ./...
# View coverage report
go tool cover -func=coverage.outThe GitHub Actions workflow (.github/workflows/ci.yml) runs on pushes and pull requests to master:
Build job — runs across Go 1.25 and 1.26:
- Verifies module dependencies (
go mod verify) - Runs static analysis (
go vet ./...) - Builds all packages (
go build -v ./...) - Runs tests with race detection and coverage profiling
- Enforces a minimum 85% code coverage threshold
Docker job — runs after the build job passes:
- Builds the Docker image to verify the
Dockerfileis valid
Triggered on version tags (v*):
- Builds static binaries for Linux, macOS, and Windows (amd64/arm64)
- Builds and pushes a Docker image to
ghcr.io/roniel-rhack/config-server-go - Creates a GitHub Release with all binaries and checksums
| Component | Library |
|---|---|
| HTTP framework | Fiber v2 |
| Configuration | Viper |
| YAML parsing | gopkg.in/yaml.v3 |
| YAML highlighting | goccy/go-yaml |
| File watching | fsnotify |
| Logging | charmbracelet/log |
| Containerization | Docker with multi-stage build |
This project is licensed under the MIT License.