Your microservice won't start locally
A microservice that will not come up on your machine is almost never a bug in your code. It is one of six things, and most of the time lost goes on not knowing which of the six. This page orders them by frequency and gives the check that rules each one out in under a minute.
Order matters: rule out from the outside in
The temptation is to open the code. It is almost always the last thing to look at, because the same commit starts in the shared environment. What changed is not the service: it is what surrounds it.
| # | Cause | What you will see | Check |
|---|---|---|---|
| 1 | Port already taken | address already in use, EADDRINUSE, or the process dies instantly |
lsof -i :8080 |
| 2 | Infrastructure down | connection refused towards 5432, 9092 or 6379 |
docker ps |
| 3 | Started too early | Sometimes it starts and sometimes it does not, with the same code | Launch it again by hand; if it works now, it was the order |
| 4 | Incomplete configuration | Fails building a bean or reading a property | Compare the variables against the shared environment's |
| 5 | Database with no schema | relation … does not exist, or migrations failing |
Connect by hand and list the tables |
| 6 | Runtime version | UnsupportedClassVersionError, syntax that will not compile |
java -version, node -v |
1. The port is already taken
It is the most frequent cause and the fastest to rule out. Every project in the same technology ships the same default port: 8080 on the JVM, 3000 on Node, 5432 on Postgres. Having two things open is enough.
# macOS and Linux
lsof -i :8080
# Windows
netstat -ano | findstr :8080
If something shows up, you have your culprit. The annoying part is not killing it: it is that tomorrow it happens again on another port, and the service calling it still points at the old one.
2. The infrastructure is not up (or not ready)
Run docker ps and see whether Kafka, Redis and Postgres are there. But mind
the trap: "up" is not "ready". A container has a PID from the first
second, and the Postgres inside it may take another twenty to accept connections. If
your service starts in that gap, it sees a connection refused and dies.
That is exactly what compose's depends_on does not solve: it waits
for the container, not for the service. That is why you need a healthcheck
and a service_healthy condition, and why the same compose file starts on a
fast machine and not on a slow one.
3. It started before the thing it calls
The symptom that gives it away: sometimes it starts and sometimes it does not, with the same code. That is a race, not an error. Your service registers itself or asks for something on start-up, and whoever has to answer was not responding yet.
Launching it again by hand confirms it in ten seconds: if it works now, it was not the code.
4. It is missing configuration the cloud gives it
In the shared environment those variables come from the deployment. On your machine you supply them, and the problem is not supplying them: it is knowing which. The list usually lives in a deployment file that is not in your repository.
You recognise this error because the service dies building, not serving: a bean that cannot be created, a mandatory property missing, a client that cannot find its base URL.
5. The database exists but is empty
Container up, connection fine, and still relation "orders" does not exist.
The migrations have not been run, or they ran against another database.
The rule worth not breaking while you chase this: do not run migrations against
the shared environment. A flyway migrate fired by accident at the
common database is a lost afternoon for the whole team, not just for you.
6. The runtime version is not the one the project expects
Last on the list because it is the least frequent, but the most misleading when it
happens: the message talks about classes or syntax and looks like a code problem. If the
project declares its version (.sdkmanrc, .nvmrc, the Maven
property), compare it with what you have.
And when it starts, the real problem begins
The six causes above are about one service. When three of them start and have to
talk to each other, the one that is on no list appears: where each one
calls. The correct URL depends on where the caller runs —a native process
reaches a container through localhost, a container reaches another through
the service name, and a container calling something native needs the special host name—
so the same dependency has three URLs depending on how each end is running.
That is what ends up hand-written in the configuration of a repository that is not yours, with the mental note not to commit it.
How Aseptic takes it out of the way
Aseptic does not guess why your code fails, and this page does not sell it as that. What it does is make five of the six causes stop happening:
- Colliding ports are remapped automatically, and the new value is what the callers receive — nobody has to be told.
- The infrastructure is started by the app, which waits for it to be healthy, not to have a PID.
- The scenario starts in dependency order, waiting for each one's health before moving on.
- Configuration is injected on start-up as
-Dproperties or environment variables, so there is no file to edit and nothing to remember to revert. - The URLs between services are rewritten with the right perspective depending on where each end runs.
The sixth —the runtime version— stays yours, and rightly so: it is your project that declares what it is built with.
If this happens to you daily, the starting point is what a local microservice environment needs, and from there running only the part you care about.
Frequently asked questions
Why does my microservice start in the shared environment but not on my machine?
Almost always because in the shared environment somebody solved for you the four things you solve yourself locally: the infrastructure, the start-up order, the URLs between services and the data. The service has not changed; what surrounds it has.
The log says «connection refused» but the container is up. Why?
Because «up» is not «ready». A container has a PID from the first second and the service inside it can take twenty more to accept connections. Compose's depends_on waits for the container, not for health.
How do I know whether the port is the problem?
See who holds it: lsof -i :8080 on macOS or Linux, netstat -ano | findstr :8080 on Windows. If something shows up, the error is not in your code.
Is it worth fixing this or living with the LOCAL_SETUP.md?
It depends how many times a day you pay for it. A forty-step document is cheap to write and expensive to maintain: it fails differently on every machine and nobody updates it when a flag changes.