Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Deploying a Multi-Module Spring Boot App to Render with PostgreSQL, Redis, Docker, and Flyway

Deploying a Spring Boot backend should be simple in theory. Build the JAR, set the environment variables, connect the database, and ship it. In practice, my deployment exposed several assumptions that worked locally but failed immediately…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Deploying a Spring Boot backend should be simple in theory. Build the JAR, set the environment variables, connect the database, and ship it.



In practice, my deployment exposed several assumptions that worked locally but failed immediately in the cloud.



I recently deployed a modular Spring Boot application to Render using Docker, Render Blueprint, PostgreSQL, Redis, Flyway migrations, Spring profiles, Hibernate/JPA, and environment variables.



The application worked locally with MySQL and Redis, but deployment exposed several production-specific issues that were easy to miss in local development. This article documents the problems, why they happened, and how I fixed them properly.






Who This Article Is For



This article is useful if you are deploying a Spring Boot application to Render and your local setup uses MySQL, Redis, Flyway, Docker, or a multi-module Maven structure.



It is especially relevant if you are moving from a local MySQL setup to PostgreSQL in the cloud.






The Stack



The backend was a Java 17 Spring Boot application with multiple Maven modules:




alagbafo/
├── api-contracts
├── core
├── users
├── orders
├── payments
├── wallet
├── notifications
├── admin
├── subscriptions
└── app






The app module was the actual Spring Boot entry point.



Locally, the project used MySQL and Redis:




spring.datasource.url=jdbc:mysql://localhost:3306/alagbafo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.data.redis.host=localhost
spring.data.redis.port=6379






For Render, the target setup was:




Spring Boot app
PostgreSQL database
Redis-compatible Key Value store
Docker deployment
Flyway migrations






Render Blueprint was the best fit because it allowed the infrastructure to be described in a render.yaml file.






Step 1: Dockerfile for a Multi-Module Spring Boot App



Because the project was a multi-module Maven application, the Dockerfile had to copy all module pom.xml files before copying the source code.



This improves Docker layer caching because dependencies can be downloaded before the full source code is copied.




# Stage 1: Build
FROM maven:3.9-eclipse-temurin-17 AS build

WORKDIR /app

COPY pom.xml .
COPY api-contracts/pom.xml api-contracts/
COPY core/pom.xml core/
COPY users/pom.xml users/
COPY orders/pom.xml orders/
COPY payments/pom.xml payments/
COPY wallet/pom.xml wallet/
COPY delivery/pom.xml delivery/
COPY notifications/pom.xml notifications/
COPY admin/pom.xml admin/
COPY support/pom.xml support/
COPY packages/pom.xml packages/
COPY subscriptions/pom.xml subscriptions/
COPY app/pom.xml app/

RUN mvn dependency:go-offline -B

COPY . .
RUN mvn clean package -DskipTests -B

# Stage 2: Run
FROM eclipse-temurin:17-jre-jammy

WORKDIR /app

COPY --from=build /app/app/target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]






This works well for Render because Render can build directly from the Dockerfile.






Step 2: The Render Blueprint



The deployment needed three resources:




  • A web service

  • A PostgreSQL database

  • A Redis service



The corrected render.yaml looked like this:




services:
- type: web
name: alagbafo
runtime: docker
dockerfilePath: ./Dockerfile
plan: free
envVars:
- key: SPRING_PROFILES_ACTIVE
value: render
- key: JAVA_OPTS
value: "-Xms256m -Xmx512m"
- key: DATABASE_URL
fromDatabase:
name: alagbafo-db
property: connectionString
- key: REDIS_URL
fromService:
type: redis
name: alagbafo-redis
property: connectionString
- key: JWT_SECRET
generateValue: true
- key: APP_BASE_URL
value: "https://alagbafo.onrender.com"
- key: PAYSTACK_SECRET_KEY
sync: false
- key: PAYSTACK_PUBLIC_KEY
sync: false
- key: PAYSTACK_WEBHOOK_SECRET
sync: false

- type: redis
name: alagbafo-redis
plan: free
ipAllowList: []

databases:
- name: alagbafo-db
plan: free
databaseName: alagbafo
ipAllowList: []






One important lesson: Redis does not belong under databases.



PostgreSQL goes under databases, while Redis goes under services with type: redis.



If Redis is declared incorrectly, Render will not inject REDIS_URL, and Spring Boot will fail at startup.






Step 3: Creating a Render-Specific Spring Profile



The first deployment failed with this:




Caused by: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure






That meant the application was still using the default MySQL configuration.



The cause was simple: the Render Spring profile was not active.



The fix was to create a Render-specific profile:




# application-render.properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.flyway.locations=classpath:db/migration-pg
server.port=${PORT:8080}






Then I made sure Render set this environment variable:




SPRING_PROFILES_ACTIVE=render






Without this, Spring Boot keeps loading application.properties, which in my case pointed to MySQL.






Problem 1: Redis URL Was Empty



The next failure was:




The URL '' is not valid for configuring Spring Data Redis.
The scheme 'null' is not supported.
Use the scheme 'redis://' for insecure or 'rediss://' for secure Redis standalone configuration.






This happened because the Redis service was declared incorrectly in render.yaml.



I initially put Redis under databases, which was wrong.



The corrected Redis service definition was:




services:
- type: redis
name: alagbafo-redis
plan: free
ipAllowList: []






Then the web service can reference it:




- key: REDIS_URL
fromService:
type: redis
name: alagbafo-redis
property: connectionString






In Spring Boot, the Render profile uses:




spring.data.redis.url=${REDIS_URL}






Avoid using an empty fallback like this:




spring.data.redis.url=${REDIS_URL:}






That makes debugging harder because Spring receives an empty string and fails with a confusing URL error.






Problem 2: Render PostgreSQL URL Is Not a JDBC URL



Render provides PostgreSQL URLs like this:




postgresql://user:password@host/database






The PostgreSQL JDBC driver expects something like this:




jdbc:postgresql://host:5432/database






At first, I tried this:




spring.datasource.url=jdbc:${DATABASE_URL}






That produced a URL like this:




jdbc:postgresql://user:password@host/database






It looked close, but it still failed:




Driver org.postgresql.Driver claims to not accept jdbcUrl






The issue was that the JDBC URL should not include credentials in that URI format.



Spring/Hikari works better with this structure:




jdbc:postgresql://host:5432/database
username=user
password=password






So I created a Render-only datasource config:




package com.alagbafo.core.config;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import javax.sql.DataSource;
import java.net.URI;

@Configuration
@Profile("render")
@ConditionalOnProperty(name = "DATABASE_URL")
public class RenderDataSourceConfig {

@Bean
public DataSource dataSource(@Value("${DATABASE_URL}") String databaseUrl) {
URI uri = URI.create(databaseUrl);

String[] userInfo = uri.getUserInfo().split(":", 2);
String username = userInfo[0];
String password = userInfo.length > 1 ? userInfo[1] : "";

int port = uri.getPort() == -1 ? 5432 : uri.getPort();
String jdbcUrl = "jdbc:postgresql://" + uri.getHost() + ":" + port + uri.getPath();

HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName("org.postgresql.Driver");
return dataSource;
}
}






This solved the datasource issue cleanly.






Step 4: Adding PostgreSQL Dependencies



The project originally only had MySQL:




<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>






For Render, I added PostgreSQL:




<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>






Since Flyway was being used, I also added PostgreSQL database support:




<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>









Problem 3: MySQL Flyway Migrations Do Not Run on PostgreSQL



The local migrations used MySQL syntax:




CREATE TABLE users_user (
id BIGINT NOT NULL AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
created_at DATETIME(6) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_users_user_email (email),
KEY idx_user_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;






PostgreSQL does not understand MySQL-specific syntax like:




AUTO_INCREMENT
DATETIME(6)
UNIQUE KEY
KEY
ENGINE=InnoDB
DEFAULT CHARSET
COLLATE=utf8mb4_unicode_ci
ON DUPLICATE KEY UPDATE






So I created a separate migration folder for PostgreSQL:




core/src/main/resources/db/migration-pg






Then the Render profile pointed Flyway there:




spring.flyway.locations=classpath:db/migration-pg






A PostgreSQL-compatible version looks like this:




CREATE TABLE users_user (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP(6) NOT NULL,
CONSTRAINT uk_users_user_user_id UNIQUE (user_id),
CONSTRAINT uk_users_user_email UNIQUE (email)
);

CREATE INDEX idx_user_created ON users_user (created_at);






For MySQL upserts like this:




ON DUPLICATE KEY UPDATE






PostgreSQL uses:




ON CONFLICT (code) DO UPDATE SET
name = EXCLUDED.name,
description = EXCLUDED.description;









Problem 4: JSON vs JSONB vs Hibernate Validation



After database connectivity was solved, Hibernate schema validation failed:




Schema-validation: wrong column type encountered in column [new_value]
in table [admin_audit_log];
found [jsonb (Types#OTHER)], but expecting [json (Types#VARCHAR)]






The entity had:




@Column(name = "new_value", columnDefinition = "JSON")
private String newValue;






But the PostgreSQL migration had created:




new_value JSONB






PostgreSQL supports both json and jsonb, but Hibernate was validating strictly against the entity definition.



I fixed the PostgreSQL migration:




old_value JSON,
new_value JSON






Because the old migration had already run on Render, I added a corrective migration:




ALTER TABLE admin_audit_log
ALTER COLUMN old_value TYPE JSON USING old_value::JSON,
ALTER COLUMN new_value TYPE JSON USING new_value::JSON;

ALTER TABLE notifications
ALTER COLUMN template_variables TYPE JSON USING template_variables::JSON;









Problem 5: Hibernate Validation Can Be Too Strict for Deployment



In local development, this was useful:




spring.jpa.hibernate.ddl-auto=validate






In a production-style environment with Flyway, I changed it to:




spring.jpa.hibernate.ddl-auto=none






This is the better setup when Flyway owns schema management.



The final Render profile became:




# application-render.properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=false

spring.flyway.locations=classpath:db/migration-pg
spring.flyway.validate-on-migrate=false

spring.data.redis.url=${REDIS_URL}
server.port=${PORT:8080}






Flyway handles migrations. Hibernate runs the app. They do not fight each other.






Environment Variables on Render



These were required:




SPRING_PROFILES_ACTIVE=render
DATABASE_URL=auto-injected by Render PostgreSQL
REDIS_URL=auto-injected by Render Redis
JWT_SECRET=secure-generated-secret






Paystack could be added later, but the app expected placeholders during setup:




PAYSTACK_SECRET_KEY=sk_test_dummy
PAYSTACK_PUBLIC_KEY=pk_test_dummy
PAYSTACK_WEBHOOK_SECRET=dummy






For a secure JWT secret, generate one locally:




openssl rand -base64 48









Final Checklist



If you are deploying a Spring Boot app to Render with Blueprint, PostgreSQL, Redis, Docker, and Flyway, check these:




  • Use New Blueprint, not just New Web Service.

  • Put PostgreSQL under databases.

  • Put Redis under services with type: redis.

  • Set SPRING_PROFILES_ACTIVE=render.

  • Do not use MySQL migrations for PostgreSQL.

  • Use separate Flyway folders for database-specific migrations.

  • Convert Render postgresql://... URL to JDBC format.

  • Let Flyway manage schema.

  • Set spring.jpa.hibernate.ddl-auto=none in deployment.

  • Add dummy values for optional third-party secrets if needed.

  • Keep real secrets in Render environment variables, not code.






Final Thoughts



The frustrating part was that every fix revealed the next layer.



First, MySQL was still being used. Then Redis was not injected. Then the PostgreSQL URL format was wrong. Then Flyway migrations needed PostgreSQL syntax. Then Hibernate schema validation complained about JSON types.



But each failure made the deployment more production-ready.



The key lesson is this: local development hides infrastructure assumptions, while cloud deployment exposes them.



Once the app had a proper Render profile, correct Blueprint config, PostgreSQL-specific Flyway migrations, and a datasource parser for Render’s database URL, the deployment became predictable.



That was the real win.

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - Deploying a Multi-Module Spring Boot App to Render with PostgreSQL, Redis, Docker, and Flyway
id: 56f99920-695d-43c8-acbe-de182ba50565
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-27
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-27"
        description = "YARA Signature for "
    strings:
        $str = "Deploying a Multi-Module Sprin" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Deploying a Multi-Module Spring Boot App")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Deploying a Multi-Module Spring Boot App*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Deploying a Multi-Module Spring Boot App"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Analyse für identifizierte Bedrohung auf Basis von Live-CTI (ENISA EUVD): CVSS 0.0 · EPSS 0.0% · CISA KEV: nein. Handlungsableitung aus den verlinkten Hersteller-Quellen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Deploying a Multi-Module Spring Boot App to Render with PostgreSQL, Redis, Docker, and Flyway

Thematisch verwandte Begriffe: Deploying, MultiModule, Spring, Boot · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-100620 | Capgo CLI (npm package @capgo/cli) through 7.98.2 is affected by an ove…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag