Nginx + Strapi + Docker

Hi community!

I’m trying to deploy an Strapi service with docker and NGINX as a reverse proxy.

Because Nginx will be used as a reverse proxy, I don’t want to do port mapping to 0.0.0.0 but just 127.0.0.1. But for some reason, curl 127.0.0.1:1337 does not returns anything. Nginx is running as a service in my Ubuntu VPS.

I suspect that it is a configuration of iptables (it is a custom VPS so I usually do not touch that part).

Here is the docker-compose.yml:

services:
  strapiDB:
    restart: unless-stopped
    env_file: .env
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: ${DATABASE_USERNAME}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
      POSTGRES_DB: ${DATABASE_NAME}
    volumes:
      - ./db_data:/var/lib/postgresql/data/ # if you want to use a bind folder
    ports:
      - "5432:5432"

  strapi:
    image: custom-strapi
    restart: unless-stopped
    env_file: .env
    environment:
      HOST: ${HOST}
      DATABASE_CLIENT: ${DATABASE_CLIENT}
      DATABASE_HOST: strapiDB
      DATABASE_PORT: ${DATABASE_PORT}
      DATABASE_NAME: ${DATABASE_NAME}
      DATABASE_USERNAME: ${DATABASE_USERNAME}
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}
      JWT_SECRET: ${JWT_SECRET}
      ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
      APP_KEYS: ${APP_KEYS}
      NODE_ENV: ${NODE_ENV}
    volumes:
      - ./config:/opt/app/config
      - ./src:/opt/app/src
      - ./package.json:/opt/package.json
      - ./yarn.lock:/opt/yarn.lock
      - ./.env:/opt/app/.env
      - ./public/uploads:/opt/app/public/uploads
    ports:
      - "127.0.0.1:1337:1337"
    depends_on:
      - strapiDB

My nginx config:

server {
        listen 443 ssl;
        server_name _; # managed by Certbot

        location / {
                proxy_pass http://127.0.0.1:1337;
        }

        ssl_certificate /etc/letsencrypt/live/_-0001/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/_-0001/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
        listen 80;
        server_name _;

    if ($host = _) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
        return 404; # managed by Certbot
}

The service look alive and I also could enter on the docker container and do curl localhost.

And about my iptables rules, there are this ones with DROP policy:

-P INPUT DROP
-P FORWARD DROP
-P OUTPUT DROP
-A DOCKER ! -i br-ae6b3f060f7a -o br-ae6b3f060f7a -j DROP
-A DOCKER ! -i br-ca83a216041b -o br-ca83a216041b -j DROP
-A DOCKER ! -i br-f56d2d8a96ef -o br-f56d2d8a96ef -j DROP
-A DOCKER ! -i docker0 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o br-f56d2d8a96ef -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o br-ca83a216041b -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o br-ae6b3f060f7a -j DROP
-A ufw-before-input -m conntrack --ctstate INVALID -j DROP
-A ufw-not-local -j DROP
-A ufw-skip-to-policy-forward -j DROP
-A ufw-skip-to-policy-input -j DROP
-A ufw-skip-to-policy-output -j DROP

At this point I dont really know if there also could be something related with host file or host inside dockers containers.

In summary, I only can make curl 127.0.0.1 when the port mapping is: 0.0.0.0:1337->1337. What can I do?

That doesn’t make sense to me, unless you have a special Docker installation where using the loopback IP doesn’t really bind to the loopback IP of your host.

.We usually need the following information to understand the issue:

1. What platform are you using? Windows, Linux or macOS? Which version of the operating systems? In case of Linux, which distribution?
2. How did you install Docker? Sharing the platform almost answers it, but only almost. Direct links to the followed guide can be useful.
3. On debian based Linux, the following commands can give us some idea and recognize incorrectly installed Docker:

docker info
docker version

Review the output before sharing and remove confidential data if any appears (public IP for example)

dpkg -l 'docker*' | grep '^ii'
snap list docker

Hi rimelek! Thank you so much for your answer.

  1. R/ Ubuntu 24.04.2 LTS.
  2. R/ I followed the Docker tutorial: Ubuntu | Docker Docs
  3. R/

Docker info output:

Client: Docker Engine - Community
 Version:    28.1.1
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.23.0
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.35.1
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 4
  Running: 4
  Paused: 0
  Stopped: 0
 Images: 4
 Server Version: 28.1.1
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 05044ec0a9a75232cad458027ca83437aae3f4da
 runc version: v1.2.5-0-g59923ef
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 6.8.0-58-generic
 Operating System: Ubuntu 24.04.2 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 6
 Total Memory: 11.68GiB
 Name: vmi2567010
 ID:
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  ::1/128
  127.0.0.0/8
 Live Restore Enabled: false

Docker version output:

Client: Docker Engine - Community
 Version:           28.1.1
 API version:       1.49
 Go version:        go1.23.8
 Git commit:        4eba377
 Built:             Fri Apr 18 09:52:14 2025
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          28.1.1
  API version:      1.49 (minimum version 1.24)
  Go version:       go1.23.8
  Git commit:       01f442b
  Built:            Fri Apr 18 09:52:14 2025
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.27
  GitCommit:        05044ec0a9a75232cad458027ca83437aae3f4da
 runc:
  Version:          1.2.5
  GitCommit:        v1.2.5-0-g59923ef
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

dpkg output:

ii  docker-buildx-plugin      0.23.0-1~ubuntu.24.04~noble   amd64        Docker Buildx cli plugin.
ii  docker-ce                 5:28.1.1-1~ubuntu.24.04~noble amd64        Docker: the open-source application container engine
ii  docker-ce-cli             5:28.1.1-1~ubuntu.24.04~noble amd64        Docker CLI: the open-source application container engine
ii  docker-ce-rootless-extras 5:28.1.1-1~ubuntu.24.04~noble amd64        Rootless support for Docker.
ii  docker-compose-plugin     2.35.1-1~ubuntu.24.04~noble   amd64        Docker Compose (V2) plugin for the Docker CLI.

snap output: error: no matching snaps installed. But this is possible because I think that I did not use snap for the installation.

So you are using docker-ce from the official repositories, and have no double installation → looks good to me.

Your use case is with the expected usage, and should not require adding any manual iptables rules.

Is nginx installed on the host itself? Judged by your nginx config, it must be.

Did you know that you can publish a container port to the ip of the docker0 interface as well? By default, it will use the 172.17.0.1. This ip is accessible from the host and every container, but not from outside the host.

Hi meyay! Thank you for your answer.

Yes it is, nginx it’s installed on the host. So I’ll do it with the ip of docker0. That could solve the issue too.

But it is strange right? I’m not crazy :sweat_smile:

Some iptables rules was added from the provider after a hacker was using the VPS to deliver phishing email.

It is indeed strange, as It should work out of the box.

I just discovered that it was because ufw active. But also even when I try to enable ufw and allow 1337 port, it does not change anything. I’ll keep it off and use only iptables for the rules.

Thank you @meyay @rimelek for your answers!

Quote

When you publish a container’s ports using Docker, traffic to and from that container gets diverted before it goes through the ufw firewall settings

Wow! The solution was, as it usually is, in the documentation. Thank you @rimelek !