# 🖼️ 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.saladpuk.com/basic/docker-1/images.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
