生产 Docker 托管 React 的容器关闭
Production Docker container hosting React shuts down
我的 dockerized React 应用程序已准备好部署,但在生产环境中大约 10 秒后关闭。我在 docker-compose.prod.yml
文件中有 restart: unless-stopped
,这使得它每 10 秒左右重新启动一次。
我尝试按照建议将 std_in: true
和 tty: 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
选项很重要如果您在 Dockerfile
或 docker-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:
这保持了服务之间的分离!希望有人觉得这有用!
我的 dockerized React 应用程序已准备好部署,但在生产环境中大约 10 秒后关闭。我在 docker-compose.prod.yml
文件中有 restart: unless-stopped
,这使得它每 10 秒左右重新启动一次。
我尝试按照建议将 std_in: true
和 tty: 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
选项很重要如果您在 Dockerfile
或 docker-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:
这保持了服务之间的分离!希望有人觉得这有用!