> For the complete documentation index, see [llms.txt](https://www.saladpuk.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.saladpuk.com/basic/docker-1/images.md).

# 🖼️ Container Image

🤠 จาก [**บทความที่ 5**](https://www.saladpuk.com/basic/docker-1/registry) เราได้รู้ว่า **`🐳Docker`** มีที่เก็บ **`🖼️ Container Images`** ขนาดใหญ่อยู่ที่ **`🌎 Docker Hub`** ดังนั้นในบทความนี้เราจะมาลองสร้าง **`🖼️ Container Image`** ของเราเองกันดูบ้างดีกั่ว

{% hint style="success" %}
**แนะนำให้อ่าน**\
บทความนี้เป็นส่วนหนึ่งของคอร์ส [🐳 **Docker**](https://www.saladpuk.com/basic/docker-1) ที่จะสอนตั้งแต่เรื่องพื้นฐานยันระดับ master กันไปเลย ซึ่งเนื้อหาทั้งหมดจะทำให้เพื่อนๆเข้าใจและใช้งาน **Docker** โดยใช้ **Kubernetes** เป็น และสามารถสร้าง **Cluster** เพื่อนำไปใช้งานบน **Cloud Providers** ต่างๆได้ และทั้งหมดที่พูดมานั้นอ่านได้ฟรีเลย ดังนั้นหากสนใจก็สามารถกดเจ้าวาฬสีน้ำเงินเพื่อไปอ่านตั้งแต่เริ่มต้นได้ครัช 🤠
{% endhint %}

## 🚨 Prerequisites

ในบทความนี้ **ดช.แมวน้ำ** จะสร้างโปรเจคโดยใช้ .NET Core และ [**Visual Studio Code**](https://code.visualstudio.com/) เป็นตัวอย่าง ดังนั้นถ้าเพื่อนๆคนไหนที่จะทำตามก็อย่าลืมตั้งตั้ง [**.NET Core 3.1 SDK**](https://dotnet.microsoft.com/download) เอาไว้ด้วยนะครับ หากใครไม่มีก็กดที่ลิงค์สีฟ้าๆไปดาวโหลดได้เลย ส่วนคนที่มีแล้วลองเปิด Command Prompt ขึ้นมาแล้วเช็คเวอร์ชั่นได้จากคำสั่งด้านล่างครับ

```
dotnet --version
```

> 3.1.403 (ขอให้มันขึ้นเป็น 3.1.xxx ก็ใช้ได้ละ xxx จะเป็นเลขอะไรก็ช่างมัน แต่ถ้าใครไม่ขึ้นแบบนี้ขอไปให้ติดตั้งโดยด่วนเบย)

## 🆕 Create .NET Core app

เมื่อติดตั้ง .NET Core 3.1 SDK เสร็จละ ถัดมาเราก็จะสร้าง **`Console Application`** ที่เป็นโปรเจคพื้นฐานเลยก็ว่าได้ ด้วยคำสั่งด้านล่างนี้

```
dotnet new console -o App -n Saladpuk
```

> ใครอยากเปลี่ยนชื่อโปรเจคก็แก้คำว่า Saladpuk เป็นชื่ออื่นเอานะ แต่ถ้าแก้ก็อย่าลืมว่าทุกขั้นตอนด้านล่างก็จะต้องใช้เป็นชื่อใหม่ของเราด้วย (แนะนำว่าให้ทำตามไปก่อนอย่าพึ่งแก้ชื่อ)

เมื่อเสร็จเราก็จะได้โฟเดอร์ชื่อ `App` ที่มีไฟล์ต่างๆอยู่ข้างในตามรูปด้านล่างนี้ครับ

![](/files/-MKgWxOxmUmHXoO6Vg3Z)

ให้เราเปิดโปรแกรม **Visual Studio Code** ขึ้นมา แล้วเลือกทำงานกับโฟเดอร์ App ของเรา โดยการคลิกที่เมนู **File** > **Open Folder ...** ตามรูปด้านล่าง

![](/files/-MKgYEHpj5qHRmRKHiKj)

ถัดมาให้เราเลือกไฟล์ `Program.cs` ที่อยู่เมนูด้านซ้ายมือ แล้วเอาโค้ดด้านล่างไปใส่แทน (อย่าลืมกดเซฟด้วยนะ)

```csharp
using System;
using System.Threading.Tasks;

namespace Saladpuk
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var counter = 0;
            while (true)
            {
                Console.WriteLine($"Counter: {++counter}");
                await Task.Delay(1000);
            }
        }
    }
}
```

เมื่อเราลอง **Run** โดยการกด **`CTRL + F5`** (ในครั้งแรกมันจะถามให้เราเลือก .NET Core) แล้วเราก็จะเห็นโปรแกรมทำการนับเลขออกมาเรื่อยๆทุกๆ 1 วินาที ตามผลลัพท์ด้านล่าง

> Counter: 1\
> Counter: 2\
> Counter: 3\
> ...

## 🛠️ Publish .NET Core app

ตอนนี้เราก็จะมีโปรเจคนับเลขง่ายๆที่พร้อมเอาไปสร้างเป็น **`🖼️ Container Image`** กันละ ดังนั้นขั้นตอนถัดไปเราก็จะเอาโปรเจคตัวนี้ไปสร้างเป็น binary ไฟล์ โดยใช้คำสั่ง **publish** เพื่อให้ได้ไฟล์ที่เหมาะสมจะเอาไปใช้งานต่อนั่นเอง

{% hint style="success" %}
**เกร็ดความรู้**\
ปรกติเวลาที่เรา Run โปรแกรมเราก็จะได้ binary ไฟล์ที่อยู่ในโฟเดอร์ bin อยู่แล้วล่ะ แต่ไฟล์พวกนั้นมันยังมีข้อมูลหลายอย่างที่ไม่เหมาะสมในการนำไปใช้งานจริง เช่นเรื่อง performance ต่างๆ ดังนั้นเวลาที่เราต้องการจะเอาโปรเจคนี้ไปใช้งาน เราควรที่จะทำการ publish เป็น **Release mode** ก่อนเสมอนั่นเองครัช
{% endhint %}

ในโปรแกรม **Visual Studio Code** ให้เปิด **`Terminal`** ขึ้นมา โดยสามารถกดได้ที่ที่เมนูด้านบนสุด **Terminal > New Terminal** แล้วเขาจะเปิดหน้าต่างใหม่ขึ้นมา ให้เราใช้คำสั่ง **publish** ตามด้านล่างได้เลยครับ

```
dotnet publish -c Release
```

จากผลกรรมที่เราได้ทำลงไป ก็จะทำให้เราได้ binary ไฟล์ขึ้นมา ซึ่งมันจะอยู่ในโฟเดอร์ตามรูปด้านล่างเลย

![App\bin\Release\netcoreapp3.1\publish](/files/-MKgfYi8I0LZGynX1uxR)

เพียงเท่านี้ก็เป็นอันเสร็จสิ้นพิธีกรรมในการเตรียมโปรเจคของเราละ ดังนั้นถัดไปก็มาเตรียมในส่วนของฝั่งพี่วาฬบ้าง

## 🐳 Create the Dockerfile

ในการสร้าง **`🖼️ Container Image`** เราจะต้องมีไฟล์ตัวหนึ่ง ที่เอาไว้บอกพี่วาฬว่าจะสร้าง Image นั้นๆขึ้นมาได้ยังไง ซึ่งเจ้าไฟล์ที่ว่าเราเรียกมันว่า **`📃 Dockerfile`** ไฟล์นั่นเอง โดยความพิเศษของเจ้าตัวนี้คือ **มันไม่มีนามสกุล** และ **ต้องอยู่บน Root โฟเดอร์** ดังนั้นใน **Visual Studio Code** เราก็จะกดปุ่มสร้างไฟล์ใหม่ขึ้นมาแล้วตั้งชื่อเป็น **Dockerfile** ตามรูปด้านล่าง

![](/files/-MKgrSGURZqus7BS2bBA)

คราวนี้ให้เราเอาโค้ดด้านล่างไปวางไว้ในไฟล์ **`📃 Dockerfile`** เลย (อย่าลืมกดเซฟด้วยนะ)

```bash
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
COPY bin/Release/netcoreapp3.1/publish/ App/
WORKDIR /App
ENTRYPOINT ["dotnet", "Saladpuk.dll"]
```

เพียงเท่านี้เราก็จะได้ไฟล์ **`Dockerfile`** ที่พร้อมจะเอาไปสร้างเป็น **`🖼️ Container Image`** กันละ

🤠 ก่อนที่เราจะไปต่อ **ดช.แมวน้ำ** อยากจะอธิบายโค้ดด้านบนต่ออีกนิสนุง เพื่อนๆจะได้เข้าใจกลไกการทำงานของมัน เวลาไปทำของตัวเองจะได้ไม่ งง นั่นเอง

## 🔎 Dockerfile

เมื่อไหร่ก็ตามที่เราสั่งให้พี่วาฬไปสร้าง **`🖼️ Container Image`** ขึ้นมาซักตัว พี่วาฬจะถามหาไฟล์ **`📃 Dockerfile`** เสมอ เพราะ **พี่วาฬไม่รู้ว่าจะกำหนด Environments ต่างๆให้มีค่าเป็นอะไรบ้าง** ดังนั้นเราเลยต้องใช้ **`Dockerfile`** เพื่อบอกพี่วาฬให้รู้ว่า Environments ต่างๆเป็นยังไงนั่นเอง

🤠 จากตรงนี้เราก็จะมาทำความเข้าใจโค้ด **`Dockerfile`** ที่อยู่ด้านบนแบบบรรทัดต่อบรรทัดกันดีกว่า

### 1️⃣ FROM

```bash
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
```

คำสั่ง **`FROM`** เป็นการ **บอกให้ Docker รู้ว่าเราจะใช้ Image ตัวอื่นด้วยนะ** ซึ่งในตัวอย่างนี้เราใช้ asp.net core เวอร์ชั่น 3.1 ของบริษัท Microsoft ซึ่งดูได้จาก **`🌎 Docker Hub`** ตามลิงค์นี้ [ASP.NET Core 2.1/3.1 Runtime](https://hub.docker.com/_/microsoft-dotnet-core-aspnet) ดังนั้นเมื่อ Docker เห็นคำสั่งนี้ Environment ของเราก็จะมีหน้าตาประมาณนี้

![](/files/-MKhNe_z3E5OwovoRVZ3)

### 2️⃣ COPY

```bash
COPY bin/Release/netcoreapp3.1/publish/ App/
```

คำสั่ง **`COPY`** เป็นการ **สั่งให้ Docker คัดลอกไฟล์จากในเครื่องเราไปเก็บใน Image ที่กำลังจะสร้าง** ซึ่งในตัวอย่างนี้เราจะคัดลอกทุกไฟล์ในโฟเดอร์ `bin/Release/netcoreapp3.1/publish` จากเครื่องเรา ไปวางไว้ใน โฟเดอร์ `App` ที่อยู่ใน Image ตามรูปด้านล่าง

> ไฟล์ที่คัดลอกไปก็คือ binary ไฟล์ต่างๆที่เราทำไว้ในขั้นตอน  🛠️ Publish .NET Core app งุย

![](/files/-MKhTJkMP77HlkIYGjl8)

### 3️⃣ WORKDIR

```bash
WORKDIR /App
```

คำสั่ง **`WORKDIR`** เป็นการ **บอกให้เจ้า Image รู้ว่าตัวมันเองจะต้องเริ่มทำงานที่โฟเดอร์ไหน** ซึ่งในตัวอย่างนี้เราจะสั่งให้มันเริ่มทำงานที่โฟเดอร์ **App** นั่นเองตามรูปด้านล่าง

![](/files/-MKhWScIKslYnJKq8vEX)

### 4️⃣ ENTRYPOINT

```bash
ENTRYPOINT ["dotnet", "Saladpuk.dll"]
```

คำสั่ง **`ENTRYPOINT`** เป็นการ **สั่งว่าเมื่อ Container ถูกสร้างขึ้นมามันจะทำงานกับไฟล์ไหน** ซึ่งในตัวอย่างนี้เราจะสั่งให้มันทำงานกับไฟล์ Saladpuk.dll โดยใช้คำสั่ง dotnet ตามรูปด้านล่างนั่นเอง&#x20;

![](/files/-MKi86YvTAIZ3omL0XTX)

🤠 จากคำสั่งทั้งหมดที่เขียนไว้ใน `Dockerfile` จะทำให้เจ้าตัว Image ของเรามีไฟล์ทุกอย่างครบถ้วน + อยู่ในโฟเดอร์ที่พร้อมทำงาน และรู้ว่าเมื่อถูกสร้างขึ้นมามันจะต้องทำงานอะไรกับไฟล์ไหนเรียบร้อยเลย ดังนั้นสุดท้ายเราก็จะมาดูคำสั่งในการสร้าง **`🖼️ Container Image`** กันต่อครัช

## 🐳 Create a container image

เมื่อของทุกอย่างของเราครบหมดละ ถัดไปเราก็จะใช้คำสั่งสุดท้ายเพื่อให้พี่วาฬของเราทำการสร้าง **`🖼️ Container Image`** ออกมาด้วยคำสั่ง **`docker build`** ตามตัวอย่างด้านล่างนี้

```
docker build -t demo01 -f Dockerfile .
```

จากคำสั่งด้านบนเป็นการบอกให้พี่วาฬทำการสร้าง **`🖼️ Container Image`** ขึ้นมา โดยกำหนดให้มีชื่อว่า **`demo01`** และบอกให้พี่วาฬหาไฟล์ Dockerfile จากโฟเดอร์ `.` ซึ่งตัว **จุด** มีความหมายว่าให้หาจากโฟเดอร์ปัจจุบันที่ทำงานอยู่

{% hint style="warning" %}
**หมายเหตุ**\
หากใครทำไม่ผ่านให้ลองเช็คดูโฟเดอร์ที่กำลังเปิด Command Prompt ว่ามันมีไฟล์ Dockerfile อยู่หรือเปล่า? และไฟล์ Dockerfile เขียนถูกและเซฟหรือยังนะจ๊ะ
{% endhint %}

เมื่อมันทำงานเสร็จเราก็จะได้ **`🖼️ Container Image`** ตัวแรกของเรามาละ ดังนั้นลองใช้คำสั่ง [**`docker images`**](https://www.saladpuk.com/basic/docker-1/exercise01#docker-images) เพื่อแสดงรายการ **Container Images** ทั้งหมดในเครื่องเราออกมาดูกัน

```
docker images
```

ซึ่งผลลัพท์ที่เราได้มาคือ มันมี **`🖼️ Container Image`** 2 ตัวอยู่ในเครื่องตามรูปด้านล่าง ส่วนสาเหตุที่เป็นแบบนั้นก็เพราะว่า Image ของเราไปใช้งาน Image ของคนอื่นด้วยยังไงล่ะ (จำได้ป่ะเราใช้ของ Microsoft ที่ชื่อว่า mcr.microsoft.com/dotnet/core/aspnet งุย)

![](/files/-MKiIlqm1SWmNs5g-G9L)

![](/files/-MKiXGVRC27hOH1DTO-F)

## 📦 Container

### 🆕 Create a container

ในเมื่อเรามี Image เรียบร้อยละ ถัดไปเราก็จะลองเอามันไปสร้างเป็น **`📦 Container`** โดยใช้คำสั่ง **`docker create`** ตามด้วย Image ID หรือ Repository แบบคำสั่งด้านล่างกัน

```
docker create demo01
```

เมื่อใช้คำสั่งด้านบนเราจะไม่เห็นอะไรเกิดขึ้น เพราะคำสั่งนี้จะเป็นการสร้าง Container ขึ้นมาเฉยๆ โดยตัวมันเองจะยังไม่เริ่มทำงานใดๆทั้งสิ้น  ตามรูปด้านล่าง

![](/files/-MKiYsjrqSLkCgGjpor8)

จากนั้นให้เราลองใช้คำสั่ง [**`docker ps -a`**](https://www.saladpuk.com/basic/docker-1/exercise01#docker-ps) เพื่อเข้าไปดูสถานะของมันหน่อย ตามโค้ดด้านล่าง

```
docker ps -a
```

เราก็จะเห็นว่า ถ้ามันเริ่มทำงานมันจะใช้คำสั่ง `dotnet Saladpuk.dll` ตามที่เรากำหนดไว้ใน **`📃 Dockerfile`** และสถานะของมันคือยัง Created ซึ่งหมายถึงถูกสร้างเฉยๆนะยังไม่ได้เริ่มทำงานใดๆทั้งสิ้น

![](/files/-MKiKHl35-xiVqOJIvoK)

### ✅ Start a container

ถ้าเราต้องการให้ **`📦 Container`** เริ่มทำงาน ก็จะใช้คำสั่ง [**`docker start`**](https://www.saladpuk.com/basic/docker-1/exercise01#docker-start) ตามด้วย Container ID ตามตัวอย่างโค้ดด้านล่าง

```
docker start e85
```

เมื่อใช้งานคำสั่งด้านบนไปเราก็จะยังไม่เห็นอะไร แต่ที่แน่ๆคือตั&#xE27;**`📦 Container`** ของเราได้เริ่มทำงานไปละ โดยเราสามารถใช้คำสั่ง [**`docker ps`**](https://www.saladpuk.com/basic/docker-1/exercise01#docker-ps) ธรรมดาเข้าไปดูสถานะของมันได้ละ

![](/files/-MKiN9uXpxWUX9u-js0e)

![](/files/-MKia2V-ZQmE-wrowkrd)

### 🔁 Attach

เมื่อเราต้องการเข้าไปดูว่า **`📦 Container`** ของเราตอนนี้มันมี **input & output** อะไรออกมาบ้าง เราก็สามารถใช้คำสั่ง `docker attach` แล้วตามด้วย Container ID ที่ต้องการเข้าไปดู แต่ในรอบนี้เราจะเพิ่ม option 1 ตัวเข้าไปคือ **`--sig-proxy=false`** เพื่อให้ Container ทำงานต่อได้เรื่อยๆแม้ว่าเราจะปิด Command Prompt ไปแล้วก็ตาม ดังนั้นก็ใช้คำสั่งด้านล่างเพื่อเข้าไปดูการทำงานของมันได้เลย

{% hint style="info" %}
โดยปรกติถ้าเราใช้คำสั่ง **`docker attach`** มันจะกำหนดค่า **`--sig-proxy`** ให้เป็น true โดย default นั่นหมายความว่าเมื่อเราปิด Command Prompt ไปเมื่อไหร่ ตัว Container ของเราก็จะหยุดการทำงานลงทันที
{% endhint %}

```
docker attach --sig-proxy=false e85
```

เราก็จะเห็นว่ามันจะนับเลขไปเรื่อยๆ ซึ่งถ้าดูจนเบื่อละก็สามารถกด **`CTRL + C`** เพื่อออกมาจาก attach mode ได้ แต่ถ้าเมื่อไหร่อยากดูการทำงานมันต่อก็สามารถใช้คำสั่งด้านบนอีกครั้ง เราก็จะเห็นว่ามันทำงานไปเรื่อยๆโดยไม่ได้สนใจว่าเราเปิดดูมันหรือเปล่านั่นเอง

![](/files/-MKibeMhwrYJXwARPrlR)

## 🐳 Docker Run

ถ้าเราต้องการที่จะสร้าง Container ขึ้นมาทำงานเพียงครั้งเดียวแล้วจบกันไป เราก็สามารถใช้คำสั่ง [**`docker run`**](https://www.saladpuk.com/basic/docker-1/exercise01#docker-run) แล้วตามด้วย Image ID หรือ Repository ได้ โดยเราจะเพิ่ม options เข้าไป 2 ตัวคือ **`-it`** ซึ่งจะทำให้ Command Promt ของเราเชื่อมต่อกับ **input & output** ที่อยู่ใน Container และ `--rm` เพื่อสั่งให้ลบ Container นี้ทิ้งทันทีที่ทำงานเสร็จ แบบคำสั่งด้านล่าง

![](/files/-MKiIlqm1SWmNs5g-G9L)

```
docker run -it --rm demo01
```

> Counter: 1\
> Counter: 2\
> Counter: 3\
> ...\
> (ถ้าดูจนเบื่อละก็สามารถกด **`CTRL + C`** เพื่อออกได้)

## 🎯 Summary

จากทั้งหมดเราก็น่าจะสร้าง **`🖼️ Container Image`** ด้วย `.NET Core` กันเป็นเรียบร้อยแล้ว และรู้ว่าไฟล์ **`📃 Dockerfile`** มีหน้าที่กำหนดว่าตัว Environments ของ Container Image จะต้องประกอบไปด้วยอะไรบ้างกันละ

ถัดมาเราก็ได้รู้ว่าเราสามารถสร้างและสั่งให้ **`📦 Container`** เริ่มทำงานได้ตามใจชอบ (สั่งให้มันหยุด [**`docker stop`**](https://www.saladpuk.com/basic/docker-1/exercise01#docker-stop) อยู่ในบทความก่อนหน้านี้แล้ว) และสุดท้ายเราสามารถจัดการ **Input & Output** ได้ที่อยู่ใน Container ได้จาก Command Prompt ของเครื่องเราเลย ด้วยคำสั่ง **`docker attach`** นั่นเอง

แม้บทความนี้จะยาวหน่อย แต่อย่างน้อยเราก็ได้เข้าใจความรู้ขั้นพื้นฐานในการสร้าง **`🖼️ Container Image`** ไปเรียบร้อยละ บทความถัดไปเราจะลองเอา **`🖼️ Container Image`** ของเราไปฝากไว้ที่ **`🌎 Docker Hub`** เพื่อแชร์ให้ทุกคนบนโลกได้ลองเล่นกันบ้างนะครัช

{% content-ref url="/pages/-ML7rc\_ig7jjCoUF7Ua1" %}
[Docker Push](/basic/docker-1/push.md)
{% endcontent-ref %}

{% hint style="success" %}
อ่านแล้วชอบป๋มก็ขอฝากแชร์ หรือกดติดตามเพื่อจะได้ไม่พลาดบทความอื่นๆจาก **ดช.แมวน้ำ** ได้จากลิงค์นี้เบยครัช [**Saladpuk Fanclub**](https://www.facebook.com/mr.saladpuk/?modal=admin_todo_tour) **😍**
{% endhint %}

![ช่องทางสนับสนุนค่าอาหารแมวน้ำกั๊ฟ 😘](/files/-MKNmp9HEqzREDsXrZ0H)
