Docker Installation (External Nginx)

In this setup, the NocoBase app container and the Nginx container run separately. You can start with Docker Installation (Built-in Nginx) and then switch the entry service to an external Nginx container.

When to use this setup

  • You want to deploy NocoBase and the web server separately
  • You want to manage the Nginx config and exposed ports yourself
  • You want to expose only the proxy container to the public network

docker-compose.yml example

If you need the full image, replace latest-no-nginx with latest-full-no-nginx.

The 13000:80 mapping is only for convenient local testing, so it does not occupy the standard host ports directly. In production, you usually do not keep using 13000:80; instead, let the external Nginx container map directly to the host 80 and 443 ports.

networks:
  nocobase:
    driver: bridge

services:
  app:
    image: nocobase/nocobase:latest-no-nginx
    restart: always
    depends_on:
      - postgres
    networks:
      - nocobase
    environment:
      - APP_KEY=your-secret-key
      - DB_DIALECT=postgres
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_DATABASE=nocobase
      - DB_USER=nocobase
      - DB_PASSWORD=nocobase
      - TZ=Etc/UTC
      - NOCOBASE_EXTRACT_CLIENT_ASSETS=true
      - NOCOBASE_PROXY_PROVIDER=nginx
      - NOCOBASE_PROXY_STORAGE_PATH=/app/nocobase/storage
      - NOCOBASE_PROXY_UPSTREAM_HOST=app
    volumes:
      - ./storage:/app/nocobase/storage

  nginx:
    image: nginx:latest
    restart: always
    depends_on:
      - app
    networks:
      - nocobase
    volumes:
      - ./storage:/app/nocobase/storage
    command: >
      /bin/sh -c '
        while [ ! -f /app/nocobase/storage/.nocobase/proxy/nginx/nocobase.conf ]; do
          echo "waiting for nocobase.conf..."
          sleep 1
        done
        rm -f /etc/nginx/conf.d/default.conf
        ln -sf /app/nocobase/storage/.nocobase/proxy/nginx/nocobase.conf /etc/nginx/conf.d/default.conf
        nginx -g "daemon off;"
      '
    ports:
      # This is only a local testing example.
      - "13000:80"
      # In production, usually change it to:
      # - "80:80"
      # - "443:443"

  postgres:
    image: postgres:16
    restart: always
    command: postgres -c wal_level=logical
    environment:
      POSTGRES_USER: nocobase
      POSTGRES_DB: nocobase
      POSTGRES_PASSWORD: nocobase
    volumes:
      - ./storage/db/postgres:/var/lib/postgresql/data
    networks:
      - nocobase

Key points

  • NOCOBASE_EXTRACT_CLIENT_ASSETS=true extracts client assets and generates proxy config
  • NOCOBASE_PROXY_PROVIDER=nginx tells the app to generate Nginx config
  • NOCOBASE_PROXY_UPSTREAM_HOST=app lets the Nginx container reach the app service through the Compose network
  • ./storage must be mounted into both the app and nginx containers so they can share proxy config, static assets, and uploaded files
  • The nginx container should wait until nocobase.conf is generated, then link it to /etc/nginx/conf.d/default.conf with ln -sf
  • The generated config forwards both the /files/ route under APP_PUBLIC_PATH and the root-level /files/ route to NocoBase for authenticated file previews and downloads
  • If you use an external Nginx container, let the nginx container handle the host port mapping. For testing, you can start with 13000:80; in production, you usually expose the host 80 and 443 ports directly, while the app service does not need to expose its port to the host

If you use a local host Nginx

If your Nginx is installed directly on the host instead of running in a Docker container, it is better to use a separate docker-compose.yml. In this setup, the app service must expose a host port directly, and the proxy-related environment variables should use host-side values.

You can use a docker-compose.yml like this:

networks:
  nocobase:
    driver: bridge

services:
  app:
    image: nocobase/nocobase:latest-no-nginx
    restart: always
    depends_on:
      - postgres
    networks:
      - nocobase
    environment:
      - APP_KEY=your-secret-key
      - DB_DIALECT=postgres
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_DATABASE=nocobase
      - DB_USER=nocobase
      - DB_PASSWORD=nocobase
      - TZ=Etc/UTC
      - NOCOBASE_EXTRACT_CLIENT_ASSETS=true
      - NOCOBASE_PROXY_PROVIDER=nginx
      - NOCOBASE_PROXY_STORAGE_PATH=/path/to/your-project/storage
      - NOCOBASE_PROXY_UPSTREAM_HOST=127.0.0.1
      - NOCOBASE_PROXY_UPSTREAM_PORT=13000
    volumes:
      - ./storage:/app/nocobase/storage
    ports:
      - "13000:13000"

  postgres:
    image: postgres:16
    restart: always
    command: postgres -c wal_level=logical
    environment:
      POSTGRES_USER: nocobase
      POSTGRES_DB: nocobase
      POSTGRES_PASSWORD: nocobase
    volumes:
      - ./storage/db/postgres:/var/lib/postgresql/data
    networks:
      - nocobase

In this variant:

  • NOCOBASE_PROXY_STORAGE_PATH should be the absolute host path of your storage directory
  • NOCOBASE_PROXY_UPSTREAM_HOST should be 127.0.0.1
  • The app service must keep ports, so the local Nginx can reach the app through 127.0.0.1:13000

After the app container starts, wait for the config file to be generated, then link it into the local Nginx config directory:

while [ ! -f ./storage/.nocobase/proxy/nginx/nocobase.conf ]; do
  echo "waiting for nocobase.conf..."
  sleep 1
done

sudo ln -sf "$(pwd)/storage/.nocobase/proxy/nginx/nocobase.conf" /etc/nginx/conf.d/nocobase.conf
sudo nginx -t
sudo systemctl reload nginx

If your host Nginx does not use the conf.d directory, replace the link target with your own config path. Usually it is safer to keep nocobase.conf as a file included from the http {} context instead of copying its content manually.

If you maintain Nginx yourself instead of using the generated config, make sure /files/ and the corresponding route under APP_PUBLIC_PATH are forwarded to NocoBase before the SPA fallback rules. See Nginx Reverse Proxy for a complete example.