生产 Docker 托管 React 的容器关闭

Production Docker container hosting React shuts down

我的 dockerized React 应用程序已准备好部署,但在生产环境中大约 10 秒后关闭。我在 docker-compose.prod.yml 文件中有 restart: unless-stopped,这使得它每 10 秒左右重新启动一次。

我尝试按照建议将 std_in: truetty: true 添加到 docker-compose.prod.yml 文件,但没有成功。

Docker 撰写:

version: "3.8"

services:
  db:
    image: postgres:13-alpine
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./data:/var/lib/postgresql/data

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
    image: "${BACKEND_IMAGE}"
    ports:
      - 8000:8000
    env_file: .env
    volumes:
      - static_volume:/backend/static
    restart: unless-stopped
    depends_on:
      - db

  # redis:
  #   image: redis:alpine
  #   restart: unless-stopped
  #   depends_on:
  #     - backend

  # celery:
  #   build:
  #     context: ./backend
  #   image: "${BACKEND_IMAGE}"
  #   command: celery -A backend worker -l info
  #   env_file: .env
  #   volumes:
  #     - ./backend/:/usr/src/app/
  #   restart: unless-stopped
  #   depends_on:
  #     - redis

  # celery-beat:
  #   build:
  #     context: ./backend
  #   image: "${BACKEND_IMAGE}"
  #   command: celery -A backend beat -l info
  #   env_file: .env
  #   volumes:
  #     - ./backend/:/usr/src/app/
  #   restart: unless-stopped
  #   depends_on:
  #     - redis

  frontend:
    image: "${FRONTEND_IMAGE}"
    stdin_open: true
    volumes:
      - frontend_build:/frontend/build
    restart: unless-stopped
    depends_on:
      - backend
    env_file: .env

  nginx:
    image: "${NGINX_IMAGE}"
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./nginx/certbot/conf:/etc/letsencrypt
      - ./nginx/certbot/www:/var/www/certbot
      - static_volume:/backend/static
      - frontend_build:/var/www/frontend
    restart: unless-stopped
    depends_on:
      - backend
      - frontend
      - db
    command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''

  certbot:
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./nginx/certbot/conf:/etc/letsencrypt
      - ./nginx/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

volumes:
  frontend_build:
  static_volume:
  media_volume:

Dockerfile.prod:

FROM node:16.13.0

# Create and set the working directory on the container
# then copy over the package.json and package-lock.json
WORKDIR /frontend
COPY package*.json ./

# Install the node packages before copying the files
RUN npm install
COPY . .

# Run the production build
CMD ["npm", "run", "build"]

docker logs [frontend_container]:

The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.

The build folder is ready to be deployed.
You may serve it with a static server:

  npm install -g serve
  serve -s build

Find out more about deployment here:

  https://cra.link/deployment


> frontend@0.1.0 build
> react-scripts build

Creating an optimized production build...

关于如何解决问题的想法?这可能是权限问题吗?

编辑:我在 docker 容器中使用 Nginx 服务:

upstream backend_server {
    server backend:8000;
}

server {
    listen 80;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location /api/ {
        return 301 https://$host$request_uri;
    }

    location /admin/ {
        return 301 https://$host$request_uri;
    }
    
    location /static/ {      
        return 301 https://$host$request_uri;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    root /var/www/frontend;
    server_name example.co www.example.co;

    ssl_certificate /etc/letsencrypt/live/exmplae.co/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.co/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location /api/ {
        proxy_pass http://backend_server$request_uri;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $https;
        proxy_connect_timeout 360s;
        proxy_read_timeout 360s;
    }

    location /admin/ {
        proxy_pass http://backend_server$request_uri;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $https;
        proxy_connect_timeout 360s;
        proxy_read_timeout 360s;
    }

    location /static/ {      
        alias /backend/static/;
    }

    location / {
        try_files $uri /index.html;
    }
}

一旦你构建了你的应用程序,你应该使用像 nginx 这样的网络服务器来为你的应用程序提供服务。因此,在您的 docker 文件中添加这些行以使用 nginx docker 映像,将您的构建复制到 运行 并将其复制到端口 80。

FROM node:16.13.0

# Create and set the working directory on the container
# then copy over the package.json and package-lock.json
WORKDIR /frontend
COPY package*.json ./

# Install the node packages before copying the files
RUN npm install


# install react-scripts if needed
RUN npm install react-scripts@3.4.1 -g --silent

COPY . .

# build your app
RUN npm run build

# production environment
FROM nginx:1.17.4-alpine
COPY --from=build /frontend/build /usr/share/nginx/html
RUN rm /etc/nginx/conf.d/default.conf
# change the left path with yours, below the file content
COPY src/nginx/nginx.conf /etc/nginx/conf.d
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

这是nginx.conf:

server {

  listen 80;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }

}

所以,其他答案都是正确的。我设置它的方式,npm run build 命令是 运行,然后容器退出。此外,由于其他答案是正确的,multi-stage 构建似乎最适用于生产映像 - 虽然我做了一些调整,以获得更好的 CI/CD 管道,我想分享。

首先,我不喜欢合并我的 frontend 构建和 nginx 构建,因为它们是独立的服务。 Nginx 不仅与前端交互,还与后端交互,我想在构建 nginx 图像之前对我的 React 代码进行 运行 前端测试。因此,我将 frontend 图像和 nginx 图像分开,以帮助我的 CI/CD 管道。

这是我的项目树结构:

.
├── Dockerfile
├── Dockerfile.prod
├── backend
├── data
├── docker-compose.ci.yml
├── docker-compose.prod.yml
├── docker-compose.yml
├── frontend
├── init-letsencrypt.sh
├── nginx
└── .github

首先,我使用 Dockerfile.ci 文件构建一个 frontend 图像,运行s 使用 CMD 选项进行测试 - CMD 选项很重要如果您在 Dockerfiledocker-compose.yml 文件中传递另一个 CMD(我们稍后会做),它将被覆盖:

FROM node:16.13.0

# Create and set the working directory on the container
# then copy over the package.json and package-lock.json
WORKDIR /frontend
COPY package*.json ./

# Install the node packages before copying the files
RUN npm install
COPY . .

# Run the development server
CMD ["npm", "run", "test"]

我的 GitHub Actions 工作流 运行 是 frontend 建立在推送到 repo 的基础上。如果测试失败,它将不会进入下一步 - 构建 nginx 图像。如果测试通过,它会将映像推送到 GitHub 容器注册表并构建 nginx 映像。

nginx图像使用frontend图像作为build图像:

# Here we use the image which was pushed to the registry
FROM ghcr.io/digibrainllc/digibrain-advertising-api/frontend:latest as build

# Then we create production build by **overriding** the previous `CMD` statment
CMD ["npm", "run", "build"]

# Production environment
FROM nginx:1.21-alpine

COPY --from=build /frontend/build /usr/share/nginx/html

RUN rm /etc/nginx/conf.d/default.conf
COPY /nginx/conf/nginx.prod.conf /etc/nginx/conf.d

这样,用于构建图像和 运行ning 测试的 docker-compose.ci 看起来像这样:

version: "3.8"

services:
  db:
    image: postgres:13-alpine
    restart: unless-stopped
    volumes:
      - ./data:/var/lib/postgresql/data
    env_file:
      - .env

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
    image: "${BACKEND_IMAGE}"
    volumes:
      - static_volume:/backend/static
      - media_volume:/backend/media
    expose:
      - 8000
    env_file: .env
    depends_on:
      - db

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.ci
    image: "${FRONTEND_IMAGE}"
    restart: unless-stopped
    environment:
      CHOKIDAR_USEPOLLING: "true"
    stdin_open: true
    env_file: .env

  nginx:
    build:
      context: .
      dockerfile: Dockerfile.prod
    image: "${NGINX_IMAGE}"
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./nginx/certbot/conf:/etc/letsencrypt
      - ./nginx/certbot/www:/var/www/certbot
      - static_volume:/backend/static
    depends_on:
      - backend
      - db
    # Could also put a `CMD` here which would run last in the `nginx` image build

volumes:
  static_volume:
  media_volume:

这保持了服务之间的分离!希望有人觉得这有用!