使用 Yarn workspace,TypeScript,esbuild,React 和 Express 構建 K8S 雲原生應用(一)

  • 2021 年 5 月 26 日
  • 筆記

本文將指導您使用 K8S
DockerYarn workspaceTypeScriptesbuildExpressReact 來設置構建一個基本的雲原生 Web 應用程序。 在本教程的最後,您將擁有一個可完全構建和部署在 K8S 上的 Web 應用程序。

設置項目

該項目將被構造為 monorepomonorepo 的目標是提高模塊之間共享的代碼量,並更好地預測這些模塊如何一起通信(例如在微服務架構中)。出於本練習的目的,我們將使結構保持簡單:

  • app,它將代表我們的 React website
  • server,它將使用 Express 服務我們的 app
  • common,其中一些代碼將在 appserver 之間共享。

設置項目之前的唯一要求是在機器上安裝 yarnYarnnpm 一樣,是一個程序包管理器,但性能更好,功能也略多。 您可以在官方文檔中閱讀有關如何安裝它的更多信息。

Workspaces(工作區)

進入到要初始化項目的文件夾,然後通過您喜歡的終端執行以下步驟:

  1. 使用 mkdir my-app 創建項目的文件夾(可以自由選擇所需的名稱)。
  2. 使用 cd my-app 進入文件夾。
  3. 使用 yarn init 初始化它。這將提示您創建初始 package.json 文件的相關問題(不用擔心,一旦創建文件,您可以隨時對其進行修改)。如果您不想使用 yarn init 命令,則始終可以手動創建文件,並將以下內容複製到其中:
{
  "name": "my-app",
  "version": "1.0.0",
  "license": "UNLICENSED",
  "private": true // Required for yarn workspace to work
}

現在,已經創建了 package.json 文件,我們需要為我們的模塊appcommonserver 創建文件夾。 為了方便 yarn workspace 發現模塊並提高項目的可讀性(readability),我們將模塊嵌套在 packages 文件夾下:

my-app/
├─ packages/ // 我們當前和將來的所有模塊都將存在的地方
│  ├─ app/
│  ├─ common/
│  ├─ server/
├─ package.json

我們的每個模塊都將充當一個小型且獨立的項目,並且需要其自己的 package.json 來管理依賴項。要設置它們中的每一個,我們既可以使用 yarn init(在每個文件夾中),也可以手動創建文件(例如,通過 IDE)。

軟件包名稱使用的命名約定是在每個軟件包之前都使用 @my-app/* 作為前綴。這在 NPM 領域中稱為作用域(您可以在此處閱讀更多內容)。您不必像這樣給自己加上前綴,但以後會有所幫助。

一旦創建並初始化了所有三個軟件包,您將具有如下所示的相似之處。

app 包:

{
  "name": "@my-app/app",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true
}

common 包:

{
  "name": "@my-app/common",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true
}

server 包:

{
  "name": "@my-app/server",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true
}

最後,我們需要告訴 yarn 在哪裡尋找模塊,所以回去編輯項目的 package.json 文件並添加以下 workspaces 屬性(如果您想了解更多有關詳細信息,請查看 Yarnworkspaces 文檔)。

{
  "name": "my-app",
  "version": "1.0",
  "license": "UNLICENSED",
  "private": true,
  "workspaces": ["packages/*"] // 在這裡添加
}

您的最終文件夾結構應如下所示:

my-app/
├─ packages/
│  ├─ app/
│  │  ├─ package.json
│  ├─ common/
│  │  ├─ package.json
│  ├─ server/
│  │  ├─ package.json
├─ package.json

現在,您已經完成了項目的基礎設置。

TypeScript

現在,我們將第一個依賴項添加到我們的項目:TypeScriptTypeScriptJavaScript 的超集,可在構建時實現類型檢查。

通過終端進入項目的根目錄,運行 yarn add -D -W typescript

  • 參數 -DTypeScript 添加到 devDependencies,因為我們僅在開發和構建期間使用它。
  • 參數 -W 允許在工作空間根目錄中安裝一個包,使其在 appcommonserver 上全局可用。

您的 package.json 應該如下所示:

{
  "name": "my-app",
  "version": "1.0",
  "license": "UNLICENSED",
  "private": true,
  "workspaces": ["packages/*"],
  "devDependencies": {
    "typescript": "^4.2.3"
  }
}

這還將創建一個 yarn.lock 文件(該文件確保在項目的整個生命周期中依賴項的預期版本保持不變)和一個 node_modules 文件夾,該文件夾保存依賴項的 binaries

現在我們已經安裝了 TypeScript,一個好習慣是告訴它如何運行。為此,我們將添加一個配置文件,該文件應由您的 IDE 拾取(如果使用 VSCode,則會自動獲取)。

在項目的根目錄下創建一個 tsconfig.json 文件,並將以下內容複製到其中:

{
  "compilerOptions": {
    /* Basic */
    "target": "es2017",
    "module": "CommonJS",
    "lib": ["ESNext", "DOM"],

    /* Modules Resolution */
    "moduleResolution": "node",
    "esModuleInterop": true,

    /* Paths Resolution */
    "baseUrl": "./",
    "paths": {
      "@flipcards/*": ["packages/*"]
    },

    /* Advanced */
    "jsx": "react",
    "experimentalDecorators": true,
    "resolveJsonModule": true
  },
  "exclude": ["node_modules", "**/node_modules/*", "dist"]
}

您可以輕鬆地搜索每個 compileoptions 屬性及其操作,但對我們最有用的是 paths 屬性。例如,這告訴 TypeScript@my-app/server@my-app/app 包中使用 @my-app/common 導入時在哪裡查找代碼和 typings

您當前的項目結構現在應如下所示:

my-app/
├─ node_modules/
├─ packages/
│  ├─ app/
│  │  ├─ package.json
│  ├─ common/
│  │  ├─ package.json
│  ├─ server/
│  │  ├─ package.json
├─ package.json
├─ tsconfig.json
├─ yarn.lock

添加第一個 script

Yarn workspace 允許我們通過 yarn workspace @my-app/* 命令模式訪問任何子包,但是每次鍵入完整的命令將變得非常多餘。為此,我們可以創建一些 helper script 方法來提升開發體驗。打開項目根目錄下的 package.json,並向其添加以下 scripts 屬性。

{
  "name": "my-app",
  "version": "1.0",
  "license": "UNLICENSED",
  "private": true,
  "workspaces": ["packages/*"],
  "devDependencies": {
    "typescript": "^4.2.3"
  },
  "scripts": {
    "app": "yarn workspace @my-app/app",
    "common": "yarn workspace @my-app/common",
    "server": "yarn workspace @my-app/server"
  }
}

現在可以像在子包中一樣執行任何命令。例如,您可以通過鍵入 yarn server add express 來添加一些新的依賴項。這將直接向 server 包添加新的依賴項。

在後續部分中,我們將開始構建前端和後端應用程序。

準備 Git

如果計劃使用 Git 作為版本控制工具,強烈建議忽略生成的文件,例如二進制文件或日誌。

為此,請在項目的根目錄下創建一個名為 .gitignore 的新文件,並將以下內容複製到其中。這將忽略本教程稍後將生成的一些文件,並避免提交大量不必要的數據。

# Logs
yarn-debug.log*
yarn-error.log*

# Binaries
node_modules/

# Builds
dist/
**/public/script.js

文件夾結構應如下所示:

my-app/
├─ packages/
├─ .gitignore
├─ package.json

添加代碼

這部分將着重於將代碼添加到我們的 commonappserver 包中。

Common

我們將從 common 開始,因為此包將由 appserver 使用。它的目標是提供共享的邏輯(shared logic)和變量(variables)。

文件

在本教程中,common 軟件包將非常簡單。首先,從添加新文件夾開始:

  • src/ 文件夾,包含包的代碼。

創建此文件夾後,將以下文件添加到其中:

src/index.ts

export const APP_TITLE = 'my-app';

現在我們有一些要導出的代碼,我們想告訴 TypeScript 從其他包中導入它時在哪裡尋找它。為此,我們將需要更新 package.json 文件:

package.json

{
  "name": "@my-app/common",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true,
  "main": "./src/index.ts" // 添加這一行來為 TS 提供入口點
}

我們現在已經完成了 common 包!

結構提醒:

common/
├─ src/
│  ├─ index.ts
├─ package.json

App

依賴項

app 包將需要以下依賴項:

從項目的根目錄運行:

  • yarn app add react react-dom
  • yarn app add -D @types/react @types/react-dom (為 TypeScript 添加類型typings)

package.json

{
  "name": "@my-app/app",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true,
  "dependencies": {
    "@my-app/common": "^0.1.0", // Notice that we've added this import manually
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  },
  "devDependencies": {
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.2"
  }
}

文件

要創建我們的 React 應用程序,我們將需要添加兩個新文件夾:

  • 一個 public/ 文件夾,它將保存基本 HTML 頁面和我們的 assets
  • 一個 src/ 文件夾,其中包含我們應用程序的代碼。

一旦創建了這兩個文件夾,我們就可以開始添加 HTML 文件,該文件將成為我們應用程序的宿主。

public/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>my-app</title>
    <meta name="description" content="Welcome on my application!" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <!-- 這個 div 是我們將注入 React 應用程序的地方 -->
    <div id="root"></div>
    <!-- 這是包含我們的應用程序的腳本的路徑 -->
    <script src="script.js"></script>
  </body>
</html>

現在我們有了要渲染的頁面,我們可以通過添加下面的兩個文件來實現非常基本但功能齊全的 React 應用程序。

src/index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';

import { App } from './App';

ReactDOM.render(<App />, document.getElementById('root'));

此代碼從我們的 HTML 文件掛接到 root div 中,並將 React組件樹 注入其中。

src/App.tsx

import { APP_TITLE } from '@flipcards/common';
import * as React from 'react';

export function App(): React.ReactElement {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <h1>Welcome on {APP_TITLE}!</h1>
      <p>
        This is the main page of our application where you can confirm that it
        is dynamic by clicking the button below.
      </p>

      <p>Current count: {count}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
    </div>
  );
}

這個簡單的 App 組件將呈現我們的應用標題和動態計數器。這將是我們的 React tree 的入口點。隨意添加您想要的任何代碼。

就是這樣!我們已經完成了非常基本的 React 應用程序。目前它並沒有太大的作用,但是我們總是可以稍後再使用它並添加更多功能。

結構提醒:

app/
├─ public/
│  ├─ index.html
├─ src/
│  ├─ App.tsx
│  ├─ index.tsx
├─ package.json

Server

依賴項

server 軟件包將需要以下依賴項:

從項目的根目錄運行:

  • yarn server add cors express
  • yarn server add -D @types/cors @types/express(為 TypeScript 添加類型typings)

package.json

{
  "name": "@my-app/server",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true,
  "dependencies": {
    "@my-app/common": "^0.1.0", // 請注意,我們已手動添加了此導入
    "cors": "^2.8.5",
    "express": "^4.17.1"
  },
  "devDependencies": {
    "@types/cors": "^2.8.10",
    "@types/express": "^4.17.11"
  }
}

文件

現在我們的 React 應用程序已經準備就緒,我們需要的最後一部分是服務器來為其提供服務。首先為其創建以下文件夾:

  • 一個 src/ 文件夾,包含我們服務器的代碼。

接下來,添加 server 的主文件:

src/index.ts

import { APP_TITLE } from '@flipcards/common';
import cors from 'cors';
import express from 'express';
import { join } from 'path';

const PORT = 3000;

const app = express();
app.use(cors());

// 服務來自 "public" 文件夾的靜態資源(例如:當有圖像要顯示時)
app.use(express.static(join(__dirname, '../../app/public')));

// 為 HTML 頁面提供服務
app.get('*', (req: any, res: any) => {
  res.sendFile(join(__dirname, '../../app/public', 'index.html'));
});

app.listen(PORT, () => {
  console.log(`${APP_TITLE}'s server listening at //localhost:${PORT}`);
});

這是一個非常基本的 Express 應用程序,但如果除了單頁應用程序之外我們沒有任何其他服務,那麼這就足夠了。

結構提醒:

server/
├─ src/
│  ├─ index.ts
├─ package.json

構建應用

Bundlers(打包構建捆綁器)

為了將 TypeScript 代碼轉換為可解釋的 JavaScript 代碼,並將所有外部庫打包到單個文件中,我們將使用打包工具。JS/TS 生態系統中有許多捆綁器,如 WebPackParcelRollup,但我們將選擇 esbuild。與其他捆綁器相比,esbuild 自帶了許多默認加載的特性(TypeScript, React),並有巨大的性能提升(快了 100 倍)。如果你有興趣了解更多,請花時間閱讀作者的常見問題解答。

這些腳本將需要以下依賴項:

  • esbuild 是我們的捆綁器
  • ts-nodeTypeScriptREPL,我們將使用它來執行腳本

從項目的根目錄運行:yarn add -D -W esbuild ts-node

package.json

{
  "name": "my-app",
  "version": "1.0",
  "license": "UNLICENSED",
  "private": true,
  "workspaces": ["packages/*"],
  "devDependencies": {
    "esbuild": "^0.9.6",
    "ts-node": "^9.1.1",
    "typescript": "^4.2.3"
  },
  "scripts": {
    "app": "yarn workspace @my-app/app",
    "common": "yarn workspace @my-app/common",
    "server": "yarn workspace @my-app/server"
  }
}

Build(編譯構建)

現在,我們擁有構建應用程序所需的所有工具,因此讓我們創建第一個腳本。

首先在項目的根目錄下創建一個名為 scripts/ 的新文件夾。

我們的腳本將用 TypeScript 編寫,並從命令行使用 ts-node 執行。儘管存在用於 esbuildCLI,但是如果您要傳遞更複雜的參數或將多個工作流組合在一起,則可以通過 JSTS 使用該庫,這更加方便。

scripts/ 文件夾中創建一個 build.ts 文件,並在下面添加代碼(我將通過注釋解釋代碼的作用):

scripts/build.ts

import { build } from 'esbuild';

/**
 * 在構建期間傳遞的通用選項。
 */
interface BuildOptions {
  env: 'production' | 'development';
}

/**
 * app 包的一個構建器函數。
 */
export async function buildApp(options: BuildOptions) {
  const { env } = options;

  await build({
    entryPoints: ['packages/app/src/index.tsx'], // 我們從這個入口點讀 React 應用程序
    outfile: 'packages/app/public/script.js', // 我們在 public/ 文件夾中輸出一個文件(請記住,在 HTML 頁面中使用了 "script.js")
    define: {
      'process.env.NODE_ENV': `"${env}"`, // 我們需要定義構建應用程序的 Node.js 環境
    },
    bundle: true,
    minify: env === 'production',
    sourcemap: env === 'development',
  });
}

/**
 * server 軟件包的構建器功能。
 */
export async function buildServer(options: BuildOptions) {
  const { env } = options;

  await build({
    entryPoints: ['packages/server/src/index.ts'],
    outfile: 'packages/server/dist/index.js',
    define: {
      'process.env.NODE_ENV': `"${env}"`,
    },
    external: ['express'], // 有些庫必須標記為外部庫
    platform: 'node', // 為 Node 構建時,我們需要為其設置環境
    target: 'node14.15.5',
    bundle: true,
    minify: env === 'production',
    sourcemap: env === 'development',
  });
}

/**
 * 所有軟件包的構建器功能。
 */
async function buildAll() {
  await Promise.all([
    buildApp({
      env: 'production',
    }),
    buildServer({
      env: 'production',
    }),
  ]);
}

// 當我們從終端使用 ts-node 運行腳本時,將執行此方法
buildAll();

該代碼很容易解釋,但是如果您覺得遺漏了部分,可以查看 esbuildAPI文檔 以獲取完整的關鍵字列表。

我們的構建腳本現已完成! 我們需要做的最後一件事是在我們的 package.json 中添加一個新命令,以方便地運行構建操作。

{
  "name": "my-app",
  "version": "1.0",
  "license": "UNLICENSED",
  "private": true,
  "workspaces": ["packages/*"],
  "devDependencies": {
    "esbuild": "^0.9.6",
    "ts-node": "^9.1.1",
    "typescript": "^4.2.3"
  },
  "scripts": {
    "app": "yarn workspace @my-app/app",
    "common": "yarn workspace @my-app/common",
    "server": "yarn workspace @my-app/server",
    "build": "ts-node ./scripts/build.ts" // Add this line here
  }
}

現在,您可以在每次對項目進行更改時從項目的根文件夾運行 yarn build 來啟動構建過程(如何添加hot-reloading,稍後討論)。

結構提醒:

my-app/
├─ packages/
├─ scripts/
│  ├─ build.ts
├─ package.json
├─ tsconfig.json

Serve(提供服務)

我們的應用程序已經構建好並可以提供給全世界使用,我們只需要向 package.json 添加最後一個命令即可:

{
  "name": "my-app",
  "version": "1.0",
  "license": "UNLICENSED",
  "private": true,
  "workspaces": ["packages/*"],
  "devDependencies": {
    "esbuild": "^0.9.6",
    "ts-node": "^9.1.1",
    "typescript": "^4.2.3"
  },
  "scripts": {
    "app": "yarn workspace @my-app/app",
    "common": "yarn workspace @my-app/common",
    "server": "yarn workspace @my-app/server",
    "build": "ts-node ./scripts/build.ts",
    "serve": "node ./packages/server/dist/index.js" // Add this line here
  }
}

由於我們現在正在處理純 JavaScript,因此可以使用 node 二進制文件啟動服務器。因此,繼續運行 yarn serve

如果您查看控制台,您將看到服務器正在成功偵聽。你也可以打開一個瀏覽器,導航到 //localhost:3000 來顯示你的 React 應用🎉!

如果你想在運行時改變端口,你可以用一個環境變量作為前綴來啟動 serve 命令: PORT=4000 yarn serve

Docker 🐳

本節將假定您已經熟悉容器的概念。

為了能夠根據我們的代碼創建鏡像,我們需要在計算機上安裝 Docker。要了解如何基於 OS 進行安裝,請花一點時間查看官方文檔

Dockerfile

要生成 Docker 鏡像,第一步是在我們項目的根目錄下創建一個 Dockerfile(這些步驟可以完全通過 CLI 來完成,但是使用配置文件是定義構建步驟的默認方式)。

FROM node:14.15.5-alpine

WORKDIR /usr/src/app

# 儘早安裝依賴項,以便如果我們應用程序中的
# 某些文件發生更改,Docker無需再次下載依賴項,
# 而是從下一步(「 COPY ..」)開始。
COPY ./package.json .
COPY ./yarn.lock .
COPY ./packages/app/package.json ./packages/app/
COPY ./packages/common/package.json ./packages/common/
COPY ./packages/server/package.json ./packages/server/
RUN yarn

# 複製我們應用程序的所有文件(.gitignore 中指定的文件除外)
COPY . .

# 編譯 app
RUN yarn build

# Port
EXPOSE 3000

# Serve
CMD [ "yarn", "serve" ]

我將嘗試儘可能詳細地說明這裡發生的事情以及這些步驟的順序為什麼很重要:

  1. FROM 告訴 Docker 將指定的基礎鏡像用於當前上下文。在我們的案例中,我們希望有一個可以運行 Node.js 應用程序的環境。
  2. WORKDIR 設置容器中的當前工作目錄。
  3. COPY 將文件或文件夾從當前本地目錄(項目的根目錄)複製到容器中的工作目錄。如您所見,在此步驟中,我們僅複製與依賴項相關的文件。這是因為 Docker 將每個構建中的命令的每個結果緩存為一層。因為我們要優化構建時間和帶寬,所以我們只想在依賴項發生更改(通常比文件更改發生的頻率小)時重新安裝它們。
  4. RUNshell 中執行命令。
  5. EXPOSE 是用於容器的內部端口(與我們的應用程序的 PORT env 無關)。 這裡的任何值都應該很好,但是如果您想了解更多信息,可以查看官方文檔
  6. CMD 的目的是提供執行容器的默認值。

如果您想了解更多有關這些關鍵字的信息,可以查看 Dockerfile參考

添加 .dockerignore

使用 .dockerignore 文件不是強制性的,但強烈建議您使用以下文件:

  • 確保您沒有將垃圾文件複製到容器中。
  • 使 COPY 命令的使用更加容易。

如果您已經熟悉它,它的工作原理就像 .gitignore 文件一樣。您可以將以下內容複製到與 Dockerfile 相同級別的 .dockerignore 文件中,該文件將被自動提取。

README.md

# Git
.gitignore

# Logs
yarn-debug.log
yarn-error.log

# Binaries
node_modules
*/*/node_modules

# Builds
*/*/build
*/*/dist
*/*/script.js

隨意添加任何您想忽略的文件,以減輕您的最終鏡像。

構建 Docker Image

現在我們的應用程序已經為 Docker 準備好了,我們需要一種從 Docker 生成實際鏡像的方法。為此,我們將向根 package.json添加一個新命令:

{
  "name": "my-app",
  "version": "1.0.0",
  "license": "MIT",
  "private": true,
  "workspaces": ["packages/*"],
  "devDependencies": {
    "esbuild": "^0.9.6",
    "ts-node": "^9.1.1",
    "typescript": "^4.2.3"
  },
  "scripts": {
    "app": "yarn workspace @my-app/app",
    "common": "yarn workspace @my-app/common",
    "server": "yarn workspace @my-app/server",
    "build": "ts-node ./scripts/build.ts",
    "serve": "node ./packages/server/dist/index.js",
    "docker": "docker build . -t my-app" // Add this line
  }
}

docker build . -t my-app 命令告訴 docker 使用當前目錄(.)查找 Dockerfile,並將生成的鏡像(-t)命名為 my-app

確保運行了 Docker 守護進程,以便在終端中使用 docker 命令。

現在該命令已經在我們項目的腳本中,您可以使用 yarn docker 運行它。

在運行該命令後,您應該期望看到以下終端輸出:

Sending build context to Docker daemon  76.16MB
Step 1/12 : FROM node:14.15.5-alpine
 ---> c1babb15a629
Step 2/12 : WORKDIR /usr/src/app
 ---> b593905aaca7
Step 3/12 : COPY ./package.json .
 ---> e0046408059c
Step 4/12 : COPY ./yarn.lock .
 ---> a91db028a6f9
Step 5/12 : COPY ./packages/app/package.json ./packages/app/
 ---> 6430ae95a2f8
Step 6/12 : COPY ./packages/common/package.json ./packages/common/
 ---> 75edad061864
Step 7/12 : COPY ./packages/server/package.json ./packages/server/
 ---> e8afa17a7645
Step 8/12 : RUN yarn
 ---> 2ca50e44a11a
Step 9/12 : COPY . .
 ---> 0642049120cf
Step 10/12 : RUN yarn build
 ---> Running in 15b224066078
yarn run v1.22.5
$ ts-node ./scripts/build.ts
Done in 3.51s.
Removing intermediate container 15b224066078
 ---> 9dce2d505c62
Step 11/12 : EXPOSE 3000
 ---> Running in f363ce55486b
Removing intermediate container f363ce55486b
 ---> 961cd1512fcf
Step 12/12 : CMD [ "yarn", "serve" ]
 ---> Running in 7debd7a72538
Removing intermediate container 7debd7a72538
 ---> df3884d6b3d6
Successfully built df3884d6b3d6
Successfully tagged my-app:latest

就是這樣!現在,我們的鏡像已創建並註冊在您的機器上,供 Docker 使用。 如果您希望列出可用的 Docker 鏡像,則可以運行 docker image ls 命令:

→ docker image ls
REPOSITORY    TAG       IMAGE ID        CREATED          SIZE
my-app        latest    df3884d6b3d6    4 minutes ago    360MB

像這樣運行命令

通過命令行運行一個可用的 Docker 鏡像非常簡單:docker run -d -p 3000:3000 my-app

  • -d 以分離模式運行容器(在後台)。
  • -p 設置暴露容器的端口(格式為[host port]:[container port])。因此,如果我們想將容器內部的端口 3000(還記得 Dockerfile 中的 EXPOSE 參數)暴露到容器外部的端口 8000,我們將把 8000:3000 傳遞給 -p 標誌。

你可以確認你的容器正在運行 docker ps。這將列出所有正在運行的容器:

如果您對啟動容器有其他要求和疑問,請在此處找到更多信息。

→ docker ps
CONTAINER ID    IMAGE     COMMAND                  CREATED          STATUS          PORTS                    NAMES
71465a89b58b    my-app    "docker-entrypoint.s…"   7 seconds ago    Up 6 seconds    0.0.0.0:3000->3000/tcp   determined_shockley

現在,打開瀏覽器並導航到以下URL //localhost:3000,查看您正在運行的應用程序🚀!

我是為少
微信:uuhells123
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)