假如你已经构建了一个 React 应用,但是现在需要部署它。应该怎么做?首先,最好选择一个云提供商,因为它们一般成本低而且部署容易。

大多数云提供商都提供了一种部署静态站点的方法。用 React 构建应用只是 JavaScript、HTML 和 CSS。它们是静态文件,几乎可以在任何 Web 服务器上使用。但实际上,如果你使用了 JSX(JS 中的 HTML)和样式化组件,那么这些可以说*只有 JavaScript*!

Docker 是用于构建和共享容器化应用的事实标准。你可以使用它打包你的应用程序,并包含多种开源 Web 服务器来为你的应用程序提供服务。另外,你还可以通过配置网络服务器来发送安全标头,这样使你的程序更安全。

前提条件:

创建 React 应用

为了集中精力,我用了一位同事已经构建的程序。首先克隆存储库。

git clone https://github.com/oktadeveloper/okta-react-styled-components-example.git react-docker
cd react-docker
npm install

这是一个使用样式化组件的 React 应用,并由 OpenID Connect(aka OIDC)保护。你可以在使用样式化组件构建 React 应用 一文中了解其创建方式。

登录你的 Okta 开发者帐户(你已经创建了一个,对吗?)注册此应用并启用 OIDC 身份验证。

  1. 转到顶部菜单中的 Applications
  2. 选择 Add Application > Single-Page App ,然后单击 Next
  3. 在设置屏幕上,为你的应用命名,例如 React Docker
  4. 确保端口设置为 3000,并且 Login redirect URIhttp://localhost:3000/callback
  5. 点击 Done

出现的界面将为你提供一个客户端 ID。

img

将客户端 ID 复制并粘贴到应用程序的 src/App.js 中。 <yourIssuerURI> 的值可以在 Okta 仪表板的 API > Authorization Servers 下找到。例如我的是 https://dev-133320.okta.com/oauth2/default

function App() {
  return (
    <Router>
      <Security issuer='<yourIssuerURI>'
                clientId='<yourClientId>'
                redirectUri={window.location.origin + '/callback'}
                pkce={true}>
        <SecureRoute path='/' exact={true} component={Calendar}/>
        <Route path='/callback' component={LoginCallback}/>
      </Security>
    </Router>
  );
}

<> 括号只是占位符,请确保将其删除!

npm start 启动你的应用。你将被重定向到 Okta 进行身份验证,然后返你的应用。如果你没有重定向,那是因为你已经登录。请在 private 窗口中重试来查看登录过程。

你会看到一个简单、干净的日历,并选择了今天的日期。

img

我承认这是一个非常简单的应用,但我们会用它来演示如何用 Docker 进行容器化。

为什么要使用Docker?

你可能会问:“为什么要用 Docker?这不会使事情复杂化吗?”

是的我同意。用 Docker 进行操作比用 Heroku 进行 firebase deploygit push 处理更为复杂。但是如果你真的要使事情复杂化,并用 Kubernetes 去管理你的应用,那么它可以给你更多的控制权。 😛

创建Dockerfile和Nginx配置

在你的根目录中创建一个 Dockerfile

FROM node:14.1-alpine AS builder

WORKDIR /opt/web
COPY package.json package-lock.json ./
RUN npm install

ENV PATH="./node_modules/.bin:$PATH"

COPY . ./
RUN npm run build

FROM nginx:1.17-alpine
RUN apk --no-cache add curl
RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.1.0/envsubst-`uname -s`-`uname -m` -o envsubst && \
    chmod +x envsubst && \
    mv envsubst /usr/local/bin
COPY ./nginx.config /etc/nginx/nginx.template
CMD ["/bin/sh", "-c", "envsubst < /etc/nginx/nginx.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"]
COPY --from=builder /opt/web/build /usr/share/nginx/html

这将会构建你的项目并把 Nginx 添加为 Web服务器。它还将安装 envsubst 版本,该版本允许你用环境变量去替换变量,并设置默认值。

在同一目录中创建一个 nginx.config

server {
    listen       ${PORT:-80};
    server_name  _;

    root /usr/share/nginx/html;
    index index.html;

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

这个文件把 Nginx 配置为将你的 React 应用作为 SPA(其中所有路由都转到 index.html)并在 80 端口上运行。在 uri 前面有两个 $$,以防止 $uri 被替换为空白值。

用 React 应用构建 Docker 镜像

先执行 docker ps 确保你的 Docker 守护进程正在运行。然后运行以下命令来构建你的 Docker 镜像。 命令中的 react-docker 可以是你想要为镜像命名的任何名字。

docker build -t react-docker .

该过程完成后,你将会看到以下消息的内容:

Successfully built 3211a1255527
Successfully tagged react-docker:latest

运行你的 Docker + React 应用

现在,你可以用 docker run 命令通过 Docker 在端口 3000 上运行 React 应用。

docker run -p 3000:80 react-docker

如果你发现这些 docker 命令很难记住,也可以在 package.json文件中添加几个脚本 。

"docker": "docker build -t react-docker .",
"react-docker": "docker run -p 3000:80 react-docker"

然后就可以用 npm run dockernpm run react-docker 运行了。

很漂亮吧?在短短几分钟内就把你的 React 应用做了 docker 化。 🎉

把将你的 React App 部署到 Heroku

你的应用要直到正式投入生产时才会真正的存在,所以让我们把它部署到 Heroku。首先我将向你展示怎样不用 Docker 做到这一点。

首先,你需要 一个 Heroku 帐户。然后,安装 Heroku CLI

打开终端,登录你的 Heroku 帐户,然后创建一个新应用。

heroku login
heroku create

现在,你应该有了一个新的 heroku Git 远程存储库。可以用 git remote -v 来确认。

在带有安全标头的根目录中创建一个 static.json 文件,并把所有 HTTP 请求重定向到 HTTPS。

{
  "headers": {
    "/**": {
      "Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' https://*.okta.com;",
      "Referrer-Policy": "no-referrer, strict-origin-when-cross-origin",
      "Strict-Transport-Security": "max-age=63072000; includeSubDomains",
      "X-Content-Type-Options": "nosniff",
      "X-Frame-Options": "DENY",
      "X-XSS-Protection": "1; mode=block",
      "Feature-Policy": "accelerometer 'none'; camera 'none'; microphone 'none'"
    }
  },
  "https_only": true,
  "root": "build/",
  "routes": {
    "/**": "index.html"
  }
}

要读取 “static.json”,你必须用 Heroku static buildpack

把你的更改提交到 Git,添加 Node.js + static buildpack,然后部署 React 应用。

git commit -am "Configure secure headers and static buildpacks"
heroku buildpacks:set heroku/nodejs
heroku buildpacks:add https://github.com/heroku/heroku-buildpack-static.git
git push heroku master

该过程完成后,使用以下方法在浏览器中打开你的应用程序:

heroku open

你将会被重定向到 Okta,可能会看到以下错误:

The 'redirect_uri' parameter must be an absolute URI that is whitelisted in the client app settings.

要解决这个问题,需要修改 Okta 应用,以将你的 Heroku URL 添加为“登录重定向 URI”。例如https://gentle-peak-37809.herokuapp.com/callback

现在,你应该可以登录并看到你的应用在 Heroku 上运行了!你可以在 https://securityheaders.com 上验证其安全标头是否正确。

img

在这个部署示例中,buildpacks 为你完成了所有工作。但是并非每个云提供商都提供 buildpack。这就是需要 Docker 的地方。

把 Docker + React App 部署到 Heroku

当涉及到 Docker 镜像时,Heroku 具有一些出色的功能。如果你的项目有一个 Dockerfile,则可以用 Heroku Container Registry直接部署你的应用。

首先,登录到Container Registry。

heroku container:login

然后,创建一个新的应用。

heroku create

把 Git URL 作为新的 remote 添加到你的应用。

git remote add docker https://git.heroku.com/<your-app-name>.git

然后,把将你的 Docker 镜像 push 到 Heroku 的 Container Registry。

heroku container:push web --remote docker

该过程完成后,release 你的应用程序镜像:

heroku container:release web --remote docker

然后,在浏览器中打开该应用:

heroku open --remote docker

你需要先在 Okta 中添加应用的 URI,然后才能登录。

改善 Docker 中 Nginx 的安全标头

如果在 securityheaders.com 上的 Docker 站点中测试新的 Nginx,你的得分应该是 F

为了解决这个问题,修改你的 nginx.config 添加安全头。

server {
    listen       ${PORT:-80};
    server_name  _;

    root /usr/share/nginx/html;
    index index.html;

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

    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' https://*.okta.com;";
    add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header Feature-Policy "accelerometer 'none'; camera 'none'; microphone 'none'";
}

更新文件后,运行以下命令:

heroku container:push web --remote docker
heroku container:release web --remote docker

现在你应该得到 A

img

用 Cloud Native Buildpacks 创建你的 React + Docker 镜像

在本文中,我们学习了把 React 应用部署到 Heroku 的两种方法。首先是利用 buildpack 和 git push。第二个是使用 Heroku 的 Container Registry 和 heroku container:push + heroku push:release

Cloud Native Buildpacks 是 Pivotal 和 Heroku 在 2018 年初发起的一项举措。它具有 pack CLI,可让你用 buildpacks 构建 Docker 映像。

我的好朋友 Joe Kutner是 Heroku 的一名软件架构师,在实现 Cloud Native Buildpacks 中发挥了重要的作用。 Joe 是 JHipster 项目的积极提交者,其作者 The Healthy Programmer 是 Cloud Native Buildpacks 核心团队的创始成员 。他对 Docker 的建议是:“如果不需要,请不要使用 Dockerfile”。

Joe 对我在弄清楚如何使用 buildpacks 创建 Docker 映像的技术上提供了很大的帮助,所以下面的说明应该归功于他。

首先,请 installpack。如果你使用的是 Mac 或 Linux,可以使用 Homebrew。如果用的是 Windows,可以安装其可执行文件

brew tap buildpack/tap
brew install pack

在前面的 buildpacks 示例中,我用了 Heroku 的 Node.js 和静态 buildpacks。

Heroku 静态构建包不是 “Cloud Native” 构建包。它使用旧的(原生云)API。这意味着它与开箱即用的 pack 不兼容。

幸运的是,Heroku 确实提供了 cnb-shim,你可以用它来使其工作。在用 cnb-shim 转换后,Joe 为 Heroku 的静态 buildpack 创建了一个 URL (https://cnb-shim.herokuapp.com/v1/heroku-community/static) 。

在本地构建和运行 Docker 镜像之前,必须先进行一项更改。 从 static.json 中删除 "https_only":true 这一行。

然后用以下命令通过 Node.js 和静态 buildpack(也就是你在 Heroku 上使用的相同 buildpack)构建 Docker 镜像。

pack build react-pack --builder heroku/buildpacks --buildpack \
  heroku/nodejs,https://cnb-shim.herokuapp.com/v1/heroku-community/static

提示:如果你想摆脱 --builder 参数,可以用 pack set-default-builder heroku/buildpacks

该过程完成后,你应该可以运行它。

docker run --rm -it --init -p 3000:3000 --env PORT=3000 okta

如果你发现这些 pack 命令很难被记住,那么可以把它们添加到 package.json 中。

"pack": "pack build react-pack --builder heroku/buildpacks --buildpack heroku/nodejs,https://cnb-shim.herokuapp.com/v1/heroku-community/static",
"react-pack": "docker run --rm -it --init -p 3000:3000 --env PORT=3000 react-pack"

然后可以使用 npm run packnpm run react-pack 来运行它们。

把将你的 React + Docker 镜像部署到 Docker Hub

通过把它们部署到 Docker Hub 等注册表中,可以轻松共享 Docker 容器。如果你还没有 Docker Hub 帐户,那就先创建一个

拥有帐户之后,登录并 push 你的镜像。在下面的示例中,我正在使用 react-docker,但你也可以使用 react-pack 来部署 buildpacks 版本。

docker login
docker image tag react-docker <your-username>/react-docker
docker push <your-username>/react-docker

默认情况下,这会将其标记为 latest。如果要标记和推送特定版本,可以用:

docker image tag react-docker <your-username>/react-docker:1.0
docker push <your-username>/react-docker

然后其他人就可以用以下命令 pull 并运行:

docker run -p 3000:80 <your-username>/react-docker

把 React + Docker 镜像部署到 Heroku

要把现有映像部署到 Heroku,可以用 docker push。你必须用以下命名约定来标记和推送镜像。

docker tag <image> registry.heroku.com/<app>/<process-type>
docker push registry.heroku.com/<app>/<process-type>

要部署 react-pack 镜像,你可以执行以下操作:

docker tag react-pack registry.heroku.com/fierce-eyrie-08414/web
docker push registry.heroku.com/fierce-eyrie-08414/web
heroku container:release web --remote docker

我尝试了一下,发现没有强制使用 HTTPS。必须将 "https_only":true 添加到 static.json 中,然后重新push。

了解有关 React 和 Docker 的更多信息

在本教程中,我们学习了如何用 Docker 容器化你的 React 应用。你可以用 docker build 手动进行这项操作,也可以用 Heroku 的 Container Registry 通过 Dockerfile 推送和发布项目。在构建容器时,还可以用 pack 命令来利用 Cloud-Native + Heroku 构建包。

如果你用的是 Heroku,它的 buildpack 比 Docker 更容易使用。通过简单的 git push,你可以在 Heroku 的服务器上部署代码并构建。

可以在 GitHub上 的 oktadeveloper/okta-react-docker-example 上找到本示例的源代码。