# Stop Running Your AI App on Your Laptop — Deploy It to the Cloud for Free

> Source: <https://dev.to/shamprakash2000/stop-running-your-ai-app-on-your-laptop-deploy-it-to-the-cloud-for-free-7e2>
> Published: 2026-09-25 14:30:00+00:00

The chat app works. It remembers conversations. It stores history in PostgreSQL.

And it runs on my laptop.

That's not a chat app. That's a script. The moment I close the terminal, it's gone. Nobody else can use it.

This article is about fixing that. We're taking the app and deploying it to the cloud so it runs 24/7 at a real URL.

There are several platforms that make deploying a Spring Boot app straightforward. Here's the honest overview:

**Railway** — fast setup, generous free tier, great developer experience. The free tier has a monthly usage limit that can catch you off guard if the app gets traffic.

**Fly.io** — powerful, runs real VMs in regions close to your users, excellent for performance-sensitive apps. The free tier is there but the setup is more involved — you configure regions, vm sizes, and scaling yourself.

**Koyeb** — solid option, deploys from Docker or GitHub, decent free tier. Less documentation than the others.

**Render** — this is what I used. Free tier, no credit card required for basic deployment, deploys directly from a GitHub repo and rebuilds on every push. The free tier does spin down after 15 minutes of inactivity, but for learning and demos it's exactly what you need.

We're going with Render.

Three things need to be true before deployment works:

You probably have 1 and 2 already. Let's talk about 3.

When you run the app locally, Java is installed on your machine. Maven is installed. The right version of everything is there.

Render's servers don't have your setup. They don't know you're using Java 17. They don't know your Maven version. They don't know your dependencies.

Docker solves this by packaging your app and everything it needs into one self-contained image. The image runs the same way everywhere — your laptop, Render, AWS, anywhere.

Think of it like a shipping container. Before shipping containers, every port had to know how to handle every type of cargo. After containers, you just move the box — the contents are someone else's problem.

A `Dockerfile` is the recipe for building that box.

Create a file called `Dockerfile` in the root of your project (same level as `pom.xml`):

```
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src ./src
RUN mvn package -DskipTests -q

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/gemini-chat-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
```

This is a **multi-stage build**. Two stages, one file.

**Stage 1 — build the app:**

`FROM maven:3.9-eclipse-temurin-17 AS build` starts from a base image that already has Maven and Java 17 installed. You name this stage `build`.

`COPY pom.xml .` then `RUN mvn dependency:go-offline` — this is a Docker trick. By copying just `pom.xml` first and downloading dependencies before copying source code, Docker can cache that layer. If you only change Java files later, Docker skips the dependency download step entirely. Builds get much faster.

`COPY src ./src` then `RUN mvn package -DskipTests` — now copy the source and compile. `-DskipTests` is intentional here; you'd run tests in CI separately.

**Stage 2 — run the app:**

`FROM eclipse-temurin:17-jre` — this is a different base image. Just the JRE (Java Runtime Environment), not the full JDK with Maven. Much smaller — a JDK image can be 500MB+, a JRE is ~200MB.

`COPY --from=build /app/target/gemini-chat-0.0.1-SNAPSHOT.jar app.jar` — copy just the compiled jar from stage 1. The Maven installation, all the source code, the intermediate build files — none of that comes along.

`EXPOSE 8080` — tells Docker (and Render) what port the app listens on.

`ENTRYPOINT ["java", "-jar", "app.jar"]` — the command that runs when the container starts.

The final image contains only the JRE and your jar. Everything else stays behind in stage 1 and is discarded.

This is something I wasn't clear on at first.

You never push a Docker image anywhere. You don't need Docker Hub. You don't need Docker installed on your machine at all.

Here's what actually happens when you deploy to Render:

`Dockerfile`)
The image lives on Render's infrastructure. Maven is only involved inside stage 1 of the build — it's just the tool that compiles the Java code. Once the image is built, Maven is gone.

The only reason to run Docker locally is if you want to test the image before pushing:

```
docker build -t gemini-chat .
docker run -p 8080:8080 -e GEMINI_API_KEY=your_key gemini-chat
```

But for deploying — GitHub to Render is all you need.

Before pushing, verify your `application.properties` uses environment variables, not hardcoded values:

```
# Gemini
spring.ai.google.gemini.api-key=${GEMINI_API_KEY}
spring.ai.google.gemini.chat.options.model=gemini-2.5-flash

# Database
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DATABASE_USERNAME}
spring.datasource.password=${DATABASE_PASSWORD}

spring.sql.init.schema-locations=classpath:chat-memory-schema.sql
spring.sql.init.mode=always
```

Every secret is read from the environment at runtime. If someone opens your GitHub repo, they see variable names, not actual values.

Also check your `.gitignore` — this should already be there, but verify:

```
# Never commit this
.env
*.env
application-local.properties
```

**Step 1 — Push your code to GitHub**

Your project needs to be in a GitHub repo. If it's not already:

```
git init
git add .
git commit -m "initial commit"
git remote add origin https://github.com/yourusername/gemini-chat.git
git push -u origin main
```

Make absolutely sure your API keys and database credentials are not in any committed file.

**Step 2 — Create a Render account**

Go to [render.com](https://render.com) and sign up. You can use GitHub to sign in — that's the easiest option since Render will need access to your repos anyway.

**Step 3 — Create a new Web Service**

In the Render dashboard:

Set the basics:

**Step 4 — Add environment variables**

In the service settings, scroll to **Environment Variables** and add:

| Key | Value | 
|---|---|
| `GEMINI_API_KEY` | your Gemini API key | 
| `DATABASE_URL` | your Neon connection string | 
| `DATABASE_USERNAME` | your Neon username | 
| `DATABASE_PASSWORD` | your Neon password | 

This is where secrets live on Render — not in your code, not in your repo. Render injects these as environment variables when the container starts.

**Step 5 — Deploy**

Click **Create Web Service**. Render will:

`https://gemini-chat-xxxx.onrender.com`
The first build takes a few minutes. After that, every push to main triggers a new build automatically.

Once the service is live, send a request to your Render URL:

```
curl -X POST https://gemini-chat-xxxx.onrender.com/api/chat-ai/session
```

You should get back a conversation ID. Then:

```
curl -X POST https://gemini-chat-xxxx.onrender.com/api/chat-ai/chat \
  -H "Content-Type: application/json" \
  -d '{"conversationId":"your-id-here","message":"Hello, are you running in the cloud?"}'
```

If you get a response — it's live. Running on Render's servers, hitting Gemini's API, storing history in Neon PostgreSQL. Not on your laptop anymore.

Render's free tier spins the service down after 15 minutes of no traffic. The first request after a sleep wakes it up, which takes about 30-60 seconds. For production you'd pay for an always-on instance, but for learning and demos the free tier is fine.

If you want to keep it warm, you can set up a simple ping from a service like UptimeRobot — free, hits your URL every 5 minutes, keeps the service awake.

Let's step back and look at what you've built:

That's a real AI backend. Not a tutorial project — a deployed service.

The app is live. Next: making the responses stream in real time — instead of waiting for the full reply, you see tokens appear as the model generates them. Same way ChatGPT works.

Deployed your first AI app? Drop the URL in the comments.

*Sham Prakash K — Backend Engineer, 4+ years in Java, Spring Boot, and distributed systems. Building AI backend infrastructure. Writing about what I actually learned, mistakes included.*
