Docker service inter-communication: Setting up redis and a web app with Docker compose
As a full time FAANG engineer for the last several years, I am insulated from most industry trends. Right now in my spare time I’m working on a JavaScript web app and wanted to try out redis and Docker.
I started with an expressjs app and plugged in Redis. It was really straightforward: I installed redis on my ubuntu laptop (yarn add redis
), and connected to it via my web app:
import { createClient } from "redis";
const redisClient = createClient();
All good. No surprises.
Next I decided to put everything in Docker, and to use docker-compose, which lets you easily declare Docker services that run in parallel. You can also have one service depend on another.
There are a couple gotchas here:
- Having one service depend on another with
depends_on
does not wait for the service to be “ready” before starting the one that depends on it; only that it was started. - Due to network isolation, accessing the redis container’s network from your app’s container no longer works with the default redis connection of
localhost:6379
.
The fixes
Below is a working docker-compose.yml
file that addresses the first point:
version: "3"
services:
my-redis-docker-service:
image: redis:latest
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 1s
timeout: 3s
retries: 5
command: ["redis-server"]
app:
depends_on:
my-redis-docker-service:
condition: service_healthy
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
- .:/app
command: yarn dev
- To fix the “ready” issue, a healthcheck is added to run
redis-cli ping
and confirm the output matches an actually running redis instance. - To fix the the networking issue and avoid this:
my-docker-app_1 | Server is running on http://localhost:3000
my-docker-app_1 | Error: connect ECONNREFUSED 127.0.0.1:6379
my-docker-app_1 | at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1595:16) {
my-docker-app_1 | errno: -111,
my-docker-app_1 | code: 'ECONNREFUSED',
my-docker-app_1 | syscall: 'connect',
my-docker-app_1 | address: '127.0.0.1',
my-docker-app_1 | port: 6379
my-docker-app_1 | }
you can use this:
import { createClient } from "redis";
export const redisClient = createClient({
url: "redis://redis-docker-service:6379", // works from Docker!
});
This is because of how networking works with docker-compose. The service name is used as a hostname in the container. In this case, the service name of my docker container is redis-docker-service
, so I’m able to connect to it with the url redis://redis-docker-service:6379
.
To have this work from within docker-compose or not, you can add
# Docker
ENV DOCKER_CONTAINER true
to your Docker
file, then check if it’s set in your TypeScript code:
// webapp.ts
const redisClient = createClient(
process.env.DOCKER_CONTAINER === "true"
? {
url: "redis://redis-docker-service:6379",
}
: { url: "redis://localhost:6379" }
);
Conclusion
I’m new to Docker and docker-compose. While Chat GPT3 has been incredibly helpful to get me going faster than ever with these tools, it did not get this right, so I spent quite a bit of time wondering why I kept getting those ECONNREFUSED errors!