惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
Webroot Blog
Webroot Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
N
News | PayPal Newsroom
S
Security Affairs
T
Tor Project blog
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security @ Cisco Blogs
H
Heimdal Security Blog
Hacker News: Ask HN
Hacker News: Ask HN
Help Net Security
Help Net Security
U
Unit 42
云风的 BLOG
云风的 BLOG
The Hacker News
The Hacker News
Cisco Talos Blog
Cisco Talos Blog
量子位
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
有赞技术团队
有赞技术团队
T
Troy Hunt's Blog
P
Privacy & Cybersecurity Law Blog
Forbes - Security
Forbes - Security
人人都是产品经理
人人都是产品经理
L
Lohrmann on Cybersecurity
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
腾讯CDC
AI
AI
Last Week in AI
Last Week in AI
Latest news
Latest news
Google Online Security Blog
Google Online Security Blog
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家
Martin Fowler
Martin Fowler
Blog — PlanetScale
Blog — PlanetScale
V2EX - 技术
V2EX - 技术
酷 壳 – CoolShell
酷 壳 – CoolShell

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Deploying a Multi-Module Spring Boot App to Render with PostgreSQL, Redis, Docker, and Flyway
Ojo Ilesanmi · 2026-06-23 · via DEV Community

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.