I have two containers App and Webserver. Webserver is plain nginx:alpine image and App is expressjs app running on port 3030 under ubuntu:focal. I heard that this is a common practise to use separate containers for application and server. So I added proxy_pass http://app:3030/;
to nginx config. Something went wrong and I digged into this a bit. To exclude incorrect nginx setup I checked raw curl requests from webserver to app container with no luck. Here is my docker-compose:
version: '3.5'
services:
webserver:
image: nginx:alpine
env_file: .env
restart: always
tty: true
ports:
- ${NGINX_HOST_HTTP_PORT}:80
- ${NGINX_HOST_HTTPS_PORT}:443
volumes:
- ${APP_CODE_PATH_HOST}:${APP_DIR}
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/sites/:/etc/nginx/conf.d/
- ./nginx/ssl/:/etc/ssl/
depends_on:
- app
container_name: ${CONTAINER_NAME_PREFIX}-webserver
networks:
- app-network
app:
env_file: .env
restart: on-failure
tty: true
build:
dockerfile: ./docker/app/Dockerfile
args:
APP_ENV: ${APP_ENV}
APP_DIR: ${APP_DIR}
PRODUCT_ID: ${PRODUCT_ID}
context: ../
environment:
- DEBIAN_FRONTEND=noninteractive
container_name: ${CONTAINER_NAME_PREFIX}-app
networks:
- app-network
networks:
app-network:
driver: bridge
I can request express from App container CLI:
/ # curl -X GET http://127.0.0.1:3030/multichannel/4034?uid=a6O5iTje8sR2PESbWpAM
{"status": "OK"}
And now from Webserver:
/ # ping app
PING app (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.090 ms
/ # curl -X GET http://172.19.0.2:3030/multichannel/4034?uid=a6O5iTje8sR2PESbWpAM
curl: (7) Failed to connect to 172.19.0.2 port 3030 after 0 ms: Connection refused
/ # curl -X GET http://app:3030/multichannel/4034?uid=a6O5iTje8sR2PESbWpAM
curl: (7) Failed to connect to app port 3030 after 0 ms: Connection refused
Nginx log (don't pay attention on IP it is correct, after containers were rebuilt it changed):
2021/10/29 15:40:46 [error] 24#24: *12 connect() failed (111: Connection refused) while connecting to upstream, client: 172.20.0.1, server: api.test, request: "GET /multichannel/4034?uid=a6O5iTje8sR2PESbWpAM HTTP/1.1", upstream: "http://172.20.0.3:3030/multichannel/4034?uid=a6O5iTje8sR2PESbWpAM", host: "api.test"
Of course Nginx conf has:
location / {
proxy_pass http://app:3030;
}
Why was connection refused? Both containers are in the same network
If you are running more than one container, you can let your containers communicate with each other by attaching them to the same network. Docker creates virtual networks which let your containers talk to each other. In a network, a container has an IP address, and optionally a hostname.
If you need to put express.js in docker, that is, dockerize your Express.js app, look no further. For the ones who are familiar with docker, just use the following dockerfile. Otherwise, we have an explanation of it line-by-line below.
Let's create a .dockerignore file and prevent node_modules from copying to the container. Done with files. This is your files structure: docker build -t imagename . In order to install node js docker container, create a new app via cli or admin panel and set a port to 8080. Done.
In this tutorial you will learn what Docker is and what purpose it serves by building a fullstack Node.js app complete with frontend and PostgreSQL database. We will use Docker Compose to connect and network each container together so that they are easy to share among project contributors, and deploy to whatever hosting service you've chosen.
According to the Docker Compose Docs: Networking: By default Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.
You need to at least expose the 3030 port on the app container to make it available to other containers:
app:
expose:
- "3030"
See the expose docs.
Alternatively, you could publish the port: either specify both ports (HOST:CONTAINER), or just the container port (a random host port will be chosen):
app:
ports:
- "3030"
Finally I found the reason why I can't get connected. When @devatherock mentioned ARTIFACTORY_URL=http://localhost:8081
I checked app code on what host is used and found this:
server.listen(3030, "127.0.0.1", () => {
logger.debug(`Server is running on ${serverConfig.port} port`);
});
Then I tried "localhost" as host and still got 502. So it is a time to read the docs. Expressjs server.listen
works like nodejs server.listen
https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback
If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. In most operating systems, listening to the unspecified IPv6 address (::) may cause the net.Server to also listen on the unspecified IPv4 address (0.0.0.0).
And the answer is to use '0.0.0.0' or just omit host parameter. 127.0.0.1 or localhost accessible only on the local machine because is bounded to the loopback interface.
As I didn't have the express app's code, I tried to simulate the issue with the docker image of another app with a simplified version of the docker-compose file and nginx config. I'm using a ubuntu machine as well. But the call from nginx to the app goes through fine in my case.
docker-compose.yml:
version: '3.5'
services:
webserver:
image: nginx:alpine
restart: always
tty: true
ports:
- 8081:80
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
container_name: test-webserver
networks:
- app-network
app:
image: devatherock/artifactory-badge:latest
environment:
- ARTIFACTORY_URL=http://localhost:8081
- ARTIFACTORY_API_KEY=dummy
restart: on-failure
tty: true
container_name: test-app
networks:
- app-network
networks:
app-network:
driver: bridge
nginx.conf:
events {
}
http {
server {
listen 80;
location / {
proxy_pass http://app:8080;
}
}
}
Even I had the same assumption as Robert, that we need to specify the ports
section for the app
service, but the call from nginx to app worked even without it. When I hit the endpoint http://localhost:8081/health
on nginx, it forwards the request to the app at http://app:8080/health
and returns the response {"status":"UP"}
that the app's health check returns
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With