สร้าง VPS บน Hostinger (รับสิทธิพิเศษ)#
-
สมัครผ่านลิงก์พิเศษ: https://www.hostinger.com/melivecode ↗ เพื่อรับส่วนลดพิเศษสำหรับผู้ติดตามช่องหมีไลฟ์โค้ด

-
ใส่โค้ด
MELIVECODEเพื่อรับส่วนลดเพิ่มเติม อีก 20%

-
ในขั้นตอนการติดตั้งระบบปฏิบัติการ เลือก Ubuntu Linux

-
เมื่อสร้าง VPS เสร็จ จะได้ IP สาธารณะสำหรับผู้ใช้
root
-
เปิด Command Prompt/Terminal แล้วเชื่อมต่อด้วยคำสั่ง ssh ด้วย
root
ssh <User>@<Public IP Address>bashใส่รหัสผ่านเพื่อเข้าสู่เซิร์ฟเวอร์

1. วัตถุประสงค์#
บทความนี้:
- ติดตั้ง Docker บน Ubuntu อย่างถูกต้อง
- บูตสแตรปโปรเจกต์ที่มีทั้ง API (Express + MySQL) และ Frontend (Next.js)
- รันทดสอบแบบ Local (ยังไม่ใช้ Docker)
- Dockerize ทั้ง API และ Frontend แล้วรันด้วย Docker Compose
2. ติดตั้ง Docker บน Ubuntu#
อ้างอิงจากคู่มือทางการของ Docker สำหรับ Ubuntu https://docs.docker.com/engine/install/ubuntu/ ↗
2.1 เตรียมระบบและติดตั้งแพ็กเกจพื้นฐาน#
sudo apt update
sudo apt install ca-certificates curlbash2.2 ติดตั้ง GPG key ของ Docker (สำหรับตรวจสอบความถูกต้องของแพ็กเกจ)#
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.ascbash2.3 เพิ่ม Docker Repository ให้กับ APT#
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullbashคำสั่งนี้ลงทะเบียน Repository อย่างเป็นทางการของ Docker เพื่อให้ติดตั้ง/อัปเดตแพ็กเกจจากแหล่งที่เชื่อถือได้
2.4 อัปเดตรายการแพ็กเกจและติดตั้ง Docker + เครื่องมือที่เกี่ยวข้อง#
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginbash2.5 ทดสอบการติดตั้งด้วยคอนเทนเนอร์ Hello World#
sudo docker run hello-worldbashสิ่งที่เกิดขึ้นเบื้องหลัง:
-
Docker จะตรวจว่ามีอิมเมจ
hello-world:latestในเครื่องหรือไม่ หากไม่มี จะเห็นข้อความUnable to find image 'hello-world:latest' locally -
จากนั้น Docker จะดึงอิมเมจจาก Docker Hub (ขึ้น
Pull complete) -
เสร็จแล้วจึงสร้างคอนเทนเนอร์และรันโปรแกรมสั้นๆ ในอิมเมจน้ัน
-
หากสำเร็จ จะแสดงข้อความยืนยัน:
plaintextHello from Docker! This message shows that your installation appears to be working correctly.
3. Project Bootstrap (Local only — ยังไม่ใช้ Docker)#
เริ่มจากโครงสร้างของโปรเจคที่โฟลเดอร์หลักจะมีทั้ง 01_api และ 02_frontend
cd 01_api
npm init -y
npm i express mysql2 cors dotenvbashสร้างไฟล์ 01_api/.gitignore:
# Node
node_modules
.env
npm-debug.log*
yarn-error.logplaintext3.1 สร้าง Express app#
สร้างไฟล์ 01_api/index.js:
const express = require('express');
const mysql = require('mysql2/promise');
const cors = require('cors');
require('dotenv').config({ path: '.env.local' });
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'test',
port: Number(process.env.DB_PORT || 3306),
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
app.get('/health', async (req, res) => {
try {
const [rows] = await pool.query('SELECT 1 AS ok');
res.json({ status: 'ok', db: rows[0].ok === 1 });
} catch (e) {
console.error(e);
res.status(500).json({ status: 'error', message: e.message });
}
});
app.get('/attractions', async (req, res) => {
try {
const [rows] = await pool.query('SELECT * FROM attraction');
res.json(rows);
} catch (e) {
console.error(e);
res.status(500).json({ error: 'Internal Server Error' });
}
});
const port = Number(process.env.PORT || 3001);
app.listen(port, () => console.log(`API listening on http://localhost:${port}`));js3.2 ตั้งค่า DB โลคัล (XAMPP)#
- เปิด XAMPP → Start Apache & MySQL
- เปิด phpMyAdmin:
http://localhost/phpmyadmin/ - สร้างฐานข้อมูล:
test - รันสคริปต์ SQL ด้านล่างเพื่อสร้าง/ใส่ข้อมูลตัวอย่าง:
CREATE TABLE `attraction` (
`id` int(11) PRIMARY KEY AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`detail` varchar(500) NOT NULL,
`coverimage` varchar(100) NOT NULL,
`latitude` decimal(11,7) NOT NULL,
`longitude` decimal(11,7) NOT NULL
);
INSERT INTO `attraction` (`id`, `name`, `detail`, `coverimage`, `latitude`, `longitude`) VALUES
(1, 'Phi Phi Islands', 'Phi Phi Islands are a group of islands in Thailand between the large island of Phuket and the Malacca Coastal Strait of Thailand.', 'https://www.melivecode.com/attractions/1.jpg', '7.7376190', '98.7068755'),
(2, 'Eiffel Tower', 'Eiffel Tower is one of the most famous structures in the world. Eiffel Tower is named after a leading French architect and engineer. It was built as a symbol of the World Fair in 1889.', 'https://www.melivecode.com/attractions/2.jpg', '48.8583736', '2.2922926'),
(3, 'Times Square', 'Times Square has become a global landmark and has become a symbol of New York City. This is a result of Times Square being a modern, futuristic venue, with huge advertising screens dotting its surroundings.', 'https://www.melivecode.com/attractions/3.jpg', '40.7589652', '-73.9893574'),
(4, 'Mount Fuji', 'Mount Fuji is the highest mountain in Japan, about 3,776 meters (12,388 feet) situated to the west of Tokyo. Mount Fuji can be seen from Tokyo on clear days.', 'https://www.melivecode.com/attractions/4.jpg', '35.3606422', '138.7186086'),
(5, 'Big Ben', 'Westminster Palace Clock Tower which is most often referred to as Big Ben. This is actually the nickname for the largest bell that hangs in the vent above the clock face.', 'https://www.melivecode.com/attractions/5.jpg', '51.5007325', '-0.1268141'),
(6, 'Taj Mahal', 'The Taj Mahal or Tachomhal is a burial building made of ivory white marble. The Taj Mahal began to be built in 1632 and was completed in 1643.', 'https://www.melivecode.com/attractions/6.jpg', '27.1751496', '78.0399535'),
(7, 'Stonehenge', 'Stonehenge is a monument prehistoric In the middle of a vast plain in the southern part of the British. The monument itself consists of 112 gigantic stone blocks arranged in 3 overlapping circles.', 'https://www.melivecode.com/attractions/7.jpg', '51.1788853', '-1.8284037'),
(8, 'Statue of Liberty', 'The Statue of Liberty is a colossal neoclassical sculpture on Liberty Island in New York Harbor in New York City, in the United States. The copper statue, a gift from the people of France to the people of the United States.', 'https://www.melivecode.com/attractions/8.jpg', '40.6891670', '-74.0444440'),
(9, 'Sydney Opera House', 'The Sydney Opera House is a multi-venue performing arts centre in Sydney. Located on the banks of the Sydney Harbour, it is often regarded as one of the most famous and distinctive buildings and a masterpiece of 20th century architecture.', 'https://www.melivecode.com/attractions/9.jpg', '-33.8586110', '151.2141670'),
(10, 'Great Pyramid of Giza', 'The Great Pyramid of Giza is the oldest and largest of the pyramids in the Giza pyramid complex bordering present-day Giza in Greater Cairo, Egypt. It is the oldest of the Seven Wonders of the Ancient World, and the only one to remain largely intact.', 'https://www.melivecode.com/attractions/10.jpg', '29.9791670', '31.1341670'),
(11, 'Hollywood Sign', 'The Hollywood Sign is an American landmark and cultural icon overlooking Hollywood, Los Angeles, California. It is situated on Mount Lee, in the Beachwood Canyon area of the Santa Monica Mountains. Spelling out the word Hollywood in 45 ft (13.7 m)-tall white capital letters and 350 feet (106.7 m) long.', 'https://www.melivecode.com/attractions/11.jpg', '34.1340610', '-118.3215920'),
(12, 'Wat Phra Kaew', 'Wat Phra Kaew, commonly known in English as the Temple of the Emerald Buddha and officially as Wat Phra Si Rattana Satsadaram, is regarded as the most sacred Buddhist temple in Thailand. The complex consists of a number of buildings within the precincts of the Grand Palace in the historical centre of Bangkok.', 'https://www.melivecode.com/attractions/12.jpg', '13.7513890', '100.4925000')sql3.3 ทดสอบ API แบบ Local#
cd 01_api
node index.jsbashเปิด:
4. สร้าง Next.js Frontend#
สร้างโปรเจกต์ไว้ข้างๆโฟลเดอร์ API:
npx create-next-app@15.5.0 02_frontendbashแก้ 02_frontend/package.json:
{
"scripts": {
"dev": "next dev --turbopack -p 3000",
"build": "next build --turbopack",
"start": "next start -p 3000",
"lint": "eslint"
}
}jsonแก้ 02_frontend/next.config.mjs:
/** @type {import('next').NextConfig} */
const API_HOST = process.env.API_HOST || 'http://localhost:3001';
const nextConfig = {
output: 'standalone',
env: {
NEXT_PUBLIC_API_HOST: API_HOST,
},
};
export default nextConfig;jsสร้าง 02_frontend/app/page.js:
"use client";
import { useState, useEffect } from "react";
export default function Page() {
const [rows, setRows] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function getAttractions() {
try {
const apiHost = process.env.NEXT_PUBLIC_API_HOST;
const res = await fetch(`${apiHost}/attractions`, { cache: "no-store" });
if (!res.ok) throw new Error("Failed to fetch");
const data = await res.json();
setRows(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
getAttractions();
}, []);
if (loading) {
return (
<main className="container">
<div className="empty">Loading...</div>
</main>
);
}
if (error) {
return (
<main className="container">
<div className="empty">Error: {error}</div>
</main>
);
}
return (
<main className="container">
<header className="header">
<h1 className="title">Attractions</h1>
<p className="subtitle">Discover points of interest nearby</p>
</header>
{!rows || rows.length === 0 ? (
<div className="empty">No attractions found.</div>
) : (
<section className="grid" aria-live="polite">
{rows.map((x) => (
<article key={x.id} className="card" tabIndex={0}>
{x.coverimage && (
<div className="media">
<img
src={x.coverimage}
alt={x.name}
className="img"
loading="lazy"
decoding="async"
/>
</div>
)}
<div className="body">
<h3 className="card-title">{x.name}</h3>
{x.detail && <p className="detail">{x.detail}</p>}
<div className="meta">
<small>
Lat: <span className="code">{x.latitude}</span> · Lng:{" "}
<span className="code">{x.longitude}</span>
</small>
</div>
</div>
</article>
))}
</section>
)}
</main>
);
}jsสร้าง 02_frontend/app/globals.css (ธีมโทนเขียวอ่อน):
:root {
--bg: #f6fdf8;
--text: #0a2e17;
--muted: #5f7a68;
--card: #ffffff;
--ring: #81c784;
--border: #cde8d3;
--shadow: 0 8px 24px rgba(104, 187, 120, 0.15);
}
/* Reset + layout */
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--bg);
color: var(--text);
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
min-height: 100%;
scroll-behavior: smooth;
}
/* Container */
.container { margin: 0 auto; padding: 2rem 1rem 3rem; max-width: 1040px; }
/* Header */
.header { display: grid; gap: .25rem; margin-bottom: 1.25rem; }
.title { font-size: clamp(1.5rem, 2.4vw, 2.25rem); line-height: 1.2; font-weight: 800; letter-spacing: -0.01em; }
.subtitle { color: var(--muted); font-size: .95rem; }
/* Empty state */
.empty {
margin-top: 2rem; padding: 2rem; border: 1px dashed var(--border);
border-radius: 12px; text-align: center; color: var(--muted);
background: color-mix(in oklab, var(--card) 85%, #d8f3dc);
}
/* Grid */
.grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); margin-top: .5rem; }
/* Card */
.card {
background: var(--card); border: 1px solid var(--border); border-radius: 14px; overflow: clip;
box-shadow: var(--shadow); display: grid; grid-template-rows: auto 1fr;
transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease; outline: none;
}
.card:focus-visible { transform: translateY(-2px); border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 40%, transparent); }
.card:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(128, 203, 161, 0.25); }
/* Media */
.media { aspect-ratio: 16 / 9; background: linear-gradient(180deg, rgba(129, 199, 132, 0.1), rgba(46, 125, 50, 0.05)); }
.img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Body */
.body { padding: 0.9rem 1rem 1rem; display: grid; gap: 0.55rem; }
.card-title { font-size: 1.05rem; font-weight: 700; letter-spacing: -0.01em; line-height: 1.35; }
.detail { color: var(--muted); font-size: .95rem; line-height: 1.55; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.meta { margin-top: .15rem; color: var(--muted); font-size: .85rem; }
.code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: .85em; background: color-mix(in oklab, var(--border) 50%, transparent); padding: .15rem .35rem; border-radius: 6px; }
/* Responsive */
@media (min-width: 768px) { .grid { gap: 1.25rem; } .body { padding: 1rem 1.05rem 1.1rem; } .card-title { font-size: 1.12rem; } .detail { -webkit-line-clamp: 3; } }
@media (max-width: 360px) { .container { padding: 1.5rem .75rem 2.25rem; } .grid { gap: .75rem; } }
@media (prefers-reduced-motion: reduce) { .card, .card:hover, .card:focus-visible { transition: none; transform: none; } }cssตัวอย่างไฟล์แวดล้อม:
02_frontend/.env.example
02_frontend/.envplaintextสร้างไฟล์ .env.example และ .env:
# API configuration for local development
API_HOST=http://localhost:3001
# Next.js configuration
NODE_ENV=development
NEXT_TELEMETRY_DISABLED=1plaintextแก้ 02_frontend/.gitignore ให้ เพิ่ม บรรทัดต่อไปนี้เพื่อให้ตัวอย่าง env ถูกติดตามใน Git:
!.env.exampleplaintext4.1 รันทั้งสองโปรเจกต์แบบ Local#
# Terminal A (API)
cd 01_api
node index.js
# Terminal B (Next.js)
cd 02_frontend
npm run devbash- Frontend → http://localhost:3000/ ↗
- API ที่ถูกเรียก → http://localhost:3001/attractions ↗

5. Dockerize ทั้งสแต็ก#
หลังทดสอบ Local ผ่านแล้ว สร้างไฟล์ Docker
5.1 Backend Dockerfile — 01_api/Dockerfile#
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3001
CMD ["node", "index.js"]dockerfile5.2 Frontend Dockerfile — 02_frontend/Dockerfile#
# ---------- Stage 1: Build the Next.js application ----------
FROM node:20-alpine AS builder
ARG API_HOST=http://localhost:3001
WORKDIR /app
COPY package*.json ./
RUN npm ci && npm cache clean --force
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1 \
API_HOST=${API_HOST}
RUN npm run build
# ---------- Stage 2: Production runtime ----------
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production \
NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]dockerfile5.3 Docker Compose — docker-compose.yml#
สำหรับ Mac ชิป M2: เปลี่ยน
phpmyadmin/phpmyadmin:latest→arm64v8/phpmyadmin
services:
mysql:
image: mysql:8.0
container_name: attractions_mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Bangkok
ports:
- "127.0.0.1:${MYSQL_PORT:-3306}:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks: [stack]
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
restart: unless-stopped
environment:
PMA_HOST: mysql
PMA_PORT: 3306
UPLOAD_LIMIT: 256M
ports:
- "${PHPMYADMIN_PORT:-8888}:80"
depends_on: [mysql]
networks: [stack]
api:
build: ./01_api
restart: unless-stopped
environment:
NODE_ENV: production
PORT: ${API_PORT}
DB_HOST: mysql
DB_PORT: ${DB_PORT}
DB_NAME: ${MYSQL_DATABASE}
DB_USER: ${MYSQL_USER}
DB_PASSWORD: ${MYSQL_PASSWORD}
TZ: Asia/Bangkok
ports:
- "${API_PORT:-3001}:3001"
depends_on: [mysql]
networks: [stack]
frontend:
build:
context: ./02_frontend
args:
- API_HOST=${API_HOST:-http://localhost:3001}
restart: unless-stopped
environment:
- NODE_ENV=production
ports:
- "${FRONTEND_PORT:-3000}:3000"
depends_on: [api]
networks: [stack]
volumes:
mysql_data:
networks:
stack:
driver: bridgeyamlไฟล์ตัวอย่างตัวแปรแวดล้อม — .env.example:
# MySQL Database Configuration
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=attractions_db
MYSQL_USER=attractions_user
MYSQL_PASSWORD=attractions_pass
MYSQL_PORT=3306
# phpMyAdmin Configuration
PHPMYADMIN_PORT=8080
# API Configuration
API_PORT=3001
DB_PORT=3306
# Frontend Configuration
FRONTEND_PORT=3000
API_HOST=http://localhost:3001plaintextสคริปต์ seed DB อัตโนมัติ — init.sql:
CREATE TABLE `attraction` (
`id` int(11) PRIMARY KEY AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`detail` varchar(500) NOT NULL,
`coverimage` varchar(100) NOT NULL,
`latitude` decimal(11,7) NOT NULL,
`longitude` decimal(11,7) NOT NULL
);
INSERT INTO `attraction` (`id`, `name`, `detail`, `coverimage`, `latitude`, `longitude`) VALUES
(1, 'Phi Phi Islands', 'Phi Phi Islands are a group of islands in Thailand between the large island of Phuket and the Malacca Coastal Strait of Thailand.', 'https://www.melivecode.com/attractions/1.jpg', '7.7376190', '98.7068755'),
(2, 'Eiffel Tower', 'Eiffel Tower is one of the most famous structures in the world. Eiffel Tower is named after a leading French architect and engineer. It was built as a symbol of the World Fair in 1889.', 'https://www.melivecode.com/attractions/2.jpg', '48.8583736', '2.2922926'),
(3, 'Times Square', 'Times Square has become a global landmark and has become a symbol of New York City. This is a result of Times Square being a modern, futuristic venue, with huge advertising screens dotting its surroundings.', 'https://www.melivecode.com/attractions/3.jpg', '40.7589652', '-73.9893574'),
(4, 'Mount Fuji', 'Mount Fuji is the highest mountain in Japan, about 3,776 meters (12,388 feet) situated to the west of Tokyo. Mount Fuji can be seen from Tokyo on clear days.', 'https://www.melivecode.com/attractions/4.jpg', '35.3606422', '138.7186086'),
(5, 'Big Ben', 'Westminster Palace Clock Tower which is most often referred to as Big Ben. This is actually the nickname for the largest bell that hangs in the vent above the clock face.', 'https://www.melivecode.com/attractions/5.jpg', '51.5007325', '-0.1268141'),
(6, 'Taj Mahal', 'The Taj Mahal or Tachomhal is a burial building made of ivory white marble. The Taj Mahal began to be built in 1632 and was completed in 1643.', 'https://www.melivecode.com/attractions/6.jpg', '27.1751496', '78.0399535'),
(7, 'Stonehenge', 'Stonehenge is a monument prehistoric In the middle of a vast plain in the southern part of the British. The monument itself consists of 112 gigantic stone blocks arranged in 3 overlapping circles.', 'https://www.melivecode.com/attractions/7.jpg', '51.1788853', '-1.8284037'),
(8, 'Statue of Liberty', 'The Statue of Liberty is a colossal neoclassical sculpture on Liberty Island in New York Harbor in New York City, in the United States. The copper statue, a gift from the people of France to the people of the United States.', 'https://www.melivecode.com/attractions/8.jpg', '40.6891670', '-74.0444440'),
(9, 'Sydney Opera House', 'The Sydney Opera House is a multi-venue performing arts centre in Sydney. Located on the banks of the Sydney Harbour, it is often regarded as one of the most famous and distinctive buildings and a masterpiece of 20th century architecture.', 'https://www.melivecode.com/attractions/9.jpg', '-33.8586110', '151.2141670'),
(10, 'Great Pyramid of Giza', 'The Great Pyramid of Giza is the oldest and largest of the pyramids in the Giza pyramid complex bordering present-day Giza in Greater Cairo, Egypt. It is the oldest of the Seven Wonders of the Ancient World, and the only one to remain largely intact.', 'https://www.melivecode.com/attractions/10.jpg', '29.9791670', '31.1341670'),
(11, 'Hollywood Sign', 'The Hollywood Sign is an American landmark and cultural icon overlooking Hollywood, Los Angeles, California. It is situated on Mount Lee, in the Beachwood Canyon area of the Santa Monica Mountains. Spelling out the word Hollywood in 45 ft (13.7 m)-tall white capital letters and 350 feet (106.7 m) long.', 'https://www.melivecode.com/attractions/11.jpg', '34.1340610', '-118.3215920'),
(12, 'Wat Phra Kaew', 'Wat Phra Kaew, commonly known in English as the Temple of the Emerald Buddha and officially as Wat Phra Si Rattana Satsadaram, is regarded as the most sacred Buddhist temple in Thailand. The complex consists of a number of buildings within the precincts of the Grand Palace in the historical centre of Bangkok.', 'https://www.melivecode.com/attractions/12.jpg', '13.7513890', '100.4925000')sql5) Publish to GitHub (Public)#
-
ลบ
.gitซ้อน ในโฟลเดอร์02_frontend- บน Windows ต้องเปิด View → Show → Hidden items เพื่อมองเห็นโฟลเดอร์ซ่อน แล้วลบ
.git
- บน Windows ต้องเปิด View → Show → Hidden items เพื่อมองเห็นโฟลเดอร์ซ่อน แล้วลบ
-
ตรวจสอบ ว่าคุณ Sign in GitHub บน VS Code แล้ว
- VS Code → Accounts → Sign in with GitHub
-
จาก VS Code เลือก Publish to GitHub (Public) ที่ Root ของโปรเจกต์ (โฟลเดอร์รวม
01_api,02_frontend,docker-compose.yml)
ได้ URL ของ GitHub แล้ว เช่น
https://github.com/<you>/<repository>.git
6) Deploy ขึ้น Cloud (VPS ที่ Hostinger)#
6.1 รีโมตไปที่ Server#
- เปิด Command Prompt/Terminal แล้วเชื่อมต่อด้วยคำสั่ง ssh ด้วย
root
ssh <User>@<Public IP Address>bash6.2 โคลนโปรเจกต์จาก GitHub#
cd ~
git clone https://github.com/<you>/<repository>.git
cd <repository>
cp .env.example .env # แก้ค่าให้เหมาะกับ Productionbashค่าแนะนำใน .env (ตัวอย่าง):
MYSQL_ROOT_PASSWORD=yourStrongRootPass
MYSQL_DATABASE=attractions_db
MYSQL_USER=attractions_user
MYSQL_PASSWORD=yourStrongDbPass
MYSQL_PORT=3306
PHPMYADMIN_PORT=8080
API_PORT=3001
DB_PORT=3306
FRONTEND_PORT=3000
API_HOST=http://<VPS Public IP Address>:3001plaintext6.3 รันด้วย Docker Compose#
docker compose up -d --build
docker compose psbash- API:
http://<SERVER_IP>:3001/attractions - Frontend:
http://<SERVER_IP>:3000/