Compare commits

...

16 Commits

Author SHA1 Message Date
1b1c6e54b1 Merge pull request 'update doc' (#9) from dev into main
Reviewed-on: #9
2025-12-25 07:56:44 +00:00
c4568a2f23 update doc 2025-12-25 07:56:03 +00:00
6000582d55 Merge pull request 'adding support for dokploy application' (#8) from dev into main
Reviewed-on: #8
2025-12-25 07:40:07 +00:00
0a2937ad55 adding support for dokploy application 2025-12-25 07:39:17 +00:00
22a244832e Merge pull request 'update doc' (#7) from dev into main
Reviewed-on: #7
2025-12-24 16:51:13 +00:00
ef2b9667fc update doc 2025-12-24 16:50:46 +00:00
c98900a9ff Merge pull request 'update doc' (#6) from dev into main
Reviewed-on: #6
2025-12-24 16:49:21 +00:00
1684099f74 update doc 2025-12-24 16:48:43 +00:00
32cb5b57d7 Merge pull request 'update README' (#5) from dev into main
Reviewed-on: #5
2025-12-24 16:45:40 +00:00
a65b2dbeb0 update README 2025-12-24 16:44:54 +00:00
6037788dbd Merge pull request 'add README.md' (#4) from dev into main
Reviewed-on: #4
2025-12-24 16:40:33 +00:00
aef24c809b add README.md 2025-12-24 16:39:16 +00:00
706631c57b Merge pull request 'dev' (#3) from dev into main
Reviewed-on: #3
2025-12-24 15:05:01 +00:00
1ee7979091 Merge pull request 'ci' (#2) from ci into dev
Reviewed-on: #2
2025-12-24 15:04:04 +00:00
12263fe0cb use java 21 instead of 17 2025-12-24 14:21:24 +00:00
668ebfcc84 adding ci 2025-12-24 14:12:03 +00:00
6 changed files with 205 additions and 7 deletions

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM gradle:8.10.2-jdk21 AS build
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts gradlew ./
COPY gradle ./gradle
RUN ./gradlew dependencies --no-daemon || true
COPY . .
RUN ./gradlew clean bootJar --no-daemon
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

143
README.md Normal file
View File

@@ -0,0 +1,143 @@
# Infisical ↔ Dokploy Bridge
A Spring Boot (Java 21) application acting as a secure bridge between Infisical and Dokploy, enabling automated synchronization and deployment of secrets through APIs and webhooks.
## Features
- Secure integration with Infisical
- Automated updates via Dokploy API
- Webhook-driven synchronization
- Docker and Docker Compose ready
## Architecture Overview
Infisical
↓ (Webhook / API)
InfisicalDokploy Bridge (Spring Boot)
↓ (Dokploy API)
Dokploy
## Requirements
- Java 21
- Docker and Docker Compose
- Infisical account
- Dokploy instance with API access
## Environment Variables
### Infisical
- INFISICAL_API_URL: Base URL of Infisical API
- INFISICAL_CLIENT_ID: Infisical service client ID
- INFISICAL_CLIENT_SECRET: Infisical service client secret
- INFISICAL_WEBHOOK_SECRET: Webhook signature validation secret
### Dokploy
- DOKPLOY_API_URL: Base URL of Dokploy API
- DOKPLOY_API_KEY: Dokploy API key
## Docker Compose
```txt
services:
infisical-bridge:
build: .
restart: always
environment:
INFISICAL_API_URL: ${INFISICAL_API_URL}
INFISICAL_CLIENT_ID: ${INFISICAL_CLIENT_ID}
INFISICAL_CLIENT_SECRET: ${INFISICAL_CLIENT_SECRET}
INFISICAL_WEBHOOK_SECRET: ${INFISICAL_WEBHOOK_SECRET}
DOKPLOY_API_URL: ${DOKPLOY_API_URL}
DOKPLOY_API_KEY: ${DOKPLOY_API_KEY}
```
## Running
With Docker Compose:
```sh
docker compose up -d --build
```
Local development:
```sh
./gradlew bootRun
```
Application runs on http://localhost:8080
Use a service like ngrok.
## Infisical Webhook Configuration
When creating a webhook in Infisical, the following rules must be respected.
### Webhook URL Formats
Infisical bridge supports two webhook URL formats, depending on the Dokploy resource you want to update.
#### Dokploy Compose Webhook
`${INFISICAL_API_URL}/webhook?dokployComposeId=${DOKPLOY_COMPOSE_ID}`
Parameters:
- dokployComposeId (required):
The identifier of the target Dokploy Compose.
This value is used to determine which Dokploy compose service should be updated when the webhook is triggered.
#### Dokploy Application Webhook
`${INFISICAL_API_URL}/webhook?dokployApplicationId=${DOKPLOY_APPLICATION_ID}`
Parameters:
- dokployApplicationId (required):
The identifier of the target Dokploy Application.
This value is used to determine which Dokploy application should be updated when the webhook is triggered.
#### Notes
- Exactly one identifier must be provided per webhook URL.
- If no identifier or multiple identifiers are provided, the webhook request will be rejected.
- Ensure the provided ID matches an existing Dokploy resource.
### Webhook Secret
The webhook secret **must exactly match**:
`${INFISICAL_WEBHOOK_SECRET}`
Requests with an invalid or missing secret will be rejected.
## Webhooks Behavior
- Incoming webhook signatures are validated
- Secrets are fetched from Infisical
- Dokploy is updated using its API
- Invalid or unsigned requests are ignored
## Security Notes
- Secrets are never persisted
- Configuration is environment-driven
- HTTPS is recommended in production
- Restrict network access to trusted sources only
## Testing
```sh
./gradlew test
```
## Tech Stack
- Java 21
- Spring Boot
- Gradle (Kotlin DSL)
- Docker / Docker Compose
## License
MIT License

11
docker-compose.yml Normal file
View File

@@ -0,0 +1,11 @@
services:
infisical-bridge:
build: .
restart: always
environment:
INFISICAL_API_URL: ${INFISICAL_API_URL}
INFISICAL_CLIENT_ID: ${INFISICAL_CLIENT_ID}
INFISICAL_CLIENT_SECRET: ${INFISICAL_CLIENT_SECRET}
INFISICAL_WEBHOOK_SECRET: ${INFISICAL_WEBHOOK_SECRET}
DOKPLOY_API_URL: ${DOKPLOY_API_URL}
DOKPLOY_API_KEY: ${DOKPLOY_API_KEY}

View File

@@ -4,6 +4,7 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import com.abnov.infisicalbridge.dto.DokployApplicationUpdateRequest;
import com.abnov.infisicalbridge.dto.DokployComposeUpdateRequest; import com.abnov.infisicalbridge.dto.DokployComposeUpdateRequest;
@FeignClient(name = "dokployClient", url = "${dokploy.api-url}", configuration = DokployFeignConfig.class) @FeignClient(name = "dokployClient", url = "${dokploy.api-url}", configuration = DokployFeignConfig.class)
@@ -11,4 +12,7 @@ public interface DokployClient {
@PostMapping("/compose.update") @PostMapping("/compose.update")
void updateCompose(@RequestBody DokployComposeUpdateRequest request); void updateCompose(@RequestBody DokployComposeUpdateRequest request);
@PostMapping("/application.update")
void updateApplication(@RequestBody DokployApplicationUpdateRequest request);
} }

View File

@@ -0,0 +1,6 @@
package com.abnov.infisicalbridge.dto;
public record DokployApplicationUpdateRequest(
String applicationId,
String env) {
}

View File

@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.abnov.infisicalbridge.dokploy.DokployClient; import com.abnov.infisicalbridge.dokploy.DokployClient;
import com.abnov.infisicalbridge.dto.DokployApplicationUpdateRequest;
import com.abnov.infisicalbridge.dto.DokployComposeUpdateRequest; import com.abnov.infisicalbridge.dto.DokployComposeUpdateRequest;
import com.abnov.infisicalbridge.dto.InfisicalWebhookEventResponse; import com.abnov.infisicalbridge.dto.InfisicalWebhookEventResponse;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@@ -38,7 +39,8 @@ public class InfisicalWebhookController {
@PostMapping @PostMapping
public ResponseEntity<Void> handleWebhook( public ResponseEntity<Void> handleWebhook(
@RequestBody String payload, @RequestBody String payload,
@RequestParam String dokployComposeId, @RequestParam(required = false) String dokployComposeId,
@RequestParam(required = false) String dokployApplicationId,
@RequestHeader(value = "X-Infisical-Signature", required = false) String signature) @RequestHeader(value = "X-Infisical-Signature", required = false) String signature)
throws InfisicalException { throws InfisicalException {
@@ -80,6 +82,7 @@ public class InfisicalWebhookController {
.map(s -> s.getSecretKey() + "=" + s.getSecretValue()) .map(s -> s.getSecretKey() + "=" + s.getSecretValue())
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
if (dokployComposeId != null) {
try { try {
dokployClient.updateCompose( dokployClient.updateCompose(
new DokployComposeUpdateRequest(dokployComposeId, envContent)); new DokployComposeUpdateRequest(dokployComposeId, envContent));
@@ -87,6 +90,17 @@ public class InfisicalWebhookController {
log.error("Failed to update Dokploy compose {}", dokployComposeId, e); log.error("Failed to update Dokploy compose {}", dokployComposeId, e);
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build(); return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build();
} }
}
if (dokployApplicationId != null) {
try {
dokployClient.updateApplication(
new DokployApplicationUpdateRequest(dokployApplicationId, envContent));
} catch (Exception e) {
log.error("Failed to update Dokploy application {}", dokployApplicationId, e);
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build();
}
}
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }