Cài Ghost CMS trên VPS Ubuntu bằng Docker aaPanel: trỏ domain, SSL và xử lý lỗi thực tế

Hướng dẫn cài Ghost CMS production bằng Docker trên Ubuntu có aaPanel, gồm Compose, reverse proxy Nginx, SSL và cách xử lý lỗi origin, ECONNREFUSED, IMAGE_SIZE_URL.

NHT
· 14 phút đọc
Minh họa Liquid Glass Ghost CMS chạy trong Docker trên VPS Ubuntu với aaPanel và lớp reverse proxy HTTPS
Hướng dẫn triển khai Ghost CMS production trên VPS Ubuntu đã cài aaPanel và Docker. Trong mô hình này, Docker/Compose quản lý Ghost và dữ liệu persistent, còn Nginx của aaPanel đảm nhiệm domain, HTTPS và reverse proxy. Bài cũng hướng dẫn kiểm tra MySQL network, nghiệm thu từng bước và xử lý các lỗi thường gặp như Request made from incorrect origin, ECONNREFUSED ...:443IMAGE_SIZE_URL.

Quick Start — 7 bước để triển khai một website Ghost

Nếu bạn đã quen Docker và aaPanel, có thể đi nhanh theo quy trình này:

  1. Chuẩn bị domain, VPS, MySQL 8 và SMTP.
  2. Tạo Ghost bằng Docker Compose, mount /var/lib/ghost/content.
  3. Bind host port vào 127.0.0.1, ví dụ 2369:2368.
  4. Kiểm tra docker compose config, docker compose pscurl http://127.0.0.1:2369/.
  5. Tạo website/domain trong aaPanel và Reverse Proxy tới http://127.0.0.1:2369.
  6. Kiểm tra Nginx, cấp SSL Let's Encrypt và mở https://domain.com/ghost/.
  7. Nếu có lỗi, kiểm tra theo thứ tự: Docker/DB → host port → aaPanel Proxy → SSL → DNS bên trong container.
Nguyên tắc quan trọng: Nếu curl http://127.0.0.1:<HOST_PORT>/ chưa trả response, chưa nên sửa Nginx.

1. Bài này dành cho mô hình nào?

Bài này dành cho trường hợp VPS Ubuntu đã cài aaPanel, Docker được quản lý từ aaPanel, còn website public được phục vụ bởi Nginx của aaPanel. Ghost chạy bên trong container và chỉ mở một host port nội bộ để Nginx chuyển tiếp request vào.

Đây là mô hình khác với bộ công cụ Ghost Docker chính thức, trong đó Ghost có thể chạy cùng Caddy, MySQL và các service bổ sung trong một stack riêng. Không nên chạy đồng thời Caddy của stack Ghost và Nginx aaPanel để cùng giữ một domain, vì cả hai sẽ tranh nhau cổng 80/443 và làm quy trình cấp SSL, log, proxy khó kiểm tra.

Kiến trúc tổng thể

                       INTERNET
                           │
                           ▼
                   domain.com:443
                           │
                           ▼
                   aaPanel / Nginx
                           │
                           ▼
                  127.0.0.1:<HOST_PORT>
                           │
                           ▼
                    Ghost :2368
                           │
                           ▼
                     MySQL :3306

Ví dụ một website mới:

Domain:     domain.com
Host port:  2369
Ghost:      2368
Proxy:      http://127.0.0.1:2369
MySQL:      mysql_M8aG:3306
Database:   your_db
Điểm dễ nhầm: 2369 là port trên VPS; 2368 là port Ghost bên trong container.

2. Hiểu đúng port, URL, MySQL host và Docker network

2.1. Mapping port

Nếu Compose có:

ports:
  - "127.0.0.1:2369:2368"

thì:

VPS:
127.0.0.1:2369

        ↓ Docker port mapping

Container:
0.0.0.0:2368

Vì vậy Nginx aaPanel phải proxy tới:

http://127.0.0.1:2369

Không proxy tới 2368 nếu host port thực tế là 2369.

Không proxy tới IP container như 172.18.0.3, vì IP container có thể thay đổi sau khi recreate.


2.2. Public URL của Ghost

Ghost production cần public URL. Nếu website dùng SSL, URL phải là:

https://domain.com

Ví dụ:

environment:
  url: https://domain.com

Không dùng:

http://localhost:2368

cho website production.

Các tùy chọn cấu hình lồng nhau của Ghost có thể truyền qua environment bằng hai dấu gạch dưới, ví dụ:

database__connection__host
database__connection__user
database__connection__password
database__connection__database

2.3. MySQL host trong Docker

Nếu Ghost và MySQL ở cùng Docker network, Ghost phải kết nối tới hostname có thể được Docker DNS phân giải, thường là service name/container name trong network phù hợp.

Ví dụ:

mysql_M8aE:3306

Không mặc định dùng:

127.0.0.1:3306

bên trong Ghost container.

127.0.0.1 trong container là chính container Ghost, không phải MySQL container.


3. Chuẩn bị VPS, domain, MySQL và SMTP

3.1. VPS

VPS nên chạy Ubuntu LTS và có quyền root hoặc tài khoản sudo.

Bạn cần:

  • Ubuntu LTS
  • aaPanel
  • Docker/Compose
  • DNS trỏ về VPS
  • MySQL 8
  • SMTP cho email giao dịch của Ghost

Ghost production cũng cần HTTPS và reverse proxy với forwarded headers phù hợp.


3.2. Domain

Domain cần có bản ghi A trỏ tới IPv4 của VPS.

Ví dụ:

Type: A
Name: @
Value: IP_VPS

Nếu dùng www:

Type: A
Name: www
Value: IP_VPS

Nếu domain có bản ghi AAAA, hãy bảo đảm IPv6 cũng trỏ đúng VPS. Nếu chưa sử dụng IPv6, có thể tạm thời gỡ AAAA để tránh một số client đi tới máy chủ khác.

Kiểm tra:

dig +short domain.com

3.3. Firewall

Mở tối thiểu:

TCP 80
TCP 443

Host port Ghost không cần public ra Internet nếu Nginx aaPanel và Ghost nằm trên cùng VPS.

Nên bind:

ports:
  - "127.0.0.1:2369:2368"

thay vì mở:

ports:
  - "2369:2368"

3.4. MySQL

Ghost production cần MySQL 8.

Bạn cần biết rõ:

MYSQL_HOST
MYSQL_PORT
MYSQL_DATABASE
MYSQL_USER
MYSQL_PASSWORD

Ví dụ:

MYSQL_HOST=mysql_M8aG
MYSQL_PORT=3306
MYSQL_DATABASE=your_db
MYSQL_USER=user_db
MYSQL_PASSWORD=<PASSWORD>
Nếu MySQL đã được tạo sẵn trong một Compose project khác, cần xác định Docker network mà MySQL đang tham gia trước khi tạo Ghost.

4. Xác định Docker network của MySQL trước khi tạo Ghost

Đây là bước rất quan trọng khi MySQL không nằm cùng Compose project với Ghost.

Ví dụ MySQL container có tên:

mysql_M8aG

Kiểm tra:

docker inspect mysql_M8aE --format '{{json .NetworkSettings.Networks}}'

Bạn sẽ thấy một hoặc nhiều network.

Ví dụ:

baota_net

Tiếp theo:

docker network inspect baota_net

Tìm mysql_M8aE trong danh sách Containers.

Nếu MySQL nằm trong network baota_net, Ghost project có thể tham gia cùng external network đó:

networks:
  mysql_network:
    external: true
    name: baota_net
Không copy baota_net một cách máy móc. Hãy dùng đúng network thực tế của MySQL trên VPS.

Checkpoint

Sau bước này bạn phải trả lời được:

MySQL container: mysql_M8aG
MySQL port:      3306
Docker network:  <network-thực-tế>
Database:        your_db
User:            user_db

Nếu chưa xác định được network, chưa nên chạy Ghost.


5. Tạo Ghost bằng Docker Compose trong aaPanel

Mở aaPanel và vào khu vực:

Docker → Compose

Tên menu có thể thay đổi theo phiên bản aaPanel. Mục tiêu là tạo một Compose project có volume persistent, không phải một container tạm thời không có dữ liệu.

5.1. Đường dẫn project không cố định

aaPanel có thể lưu Compose project trong thư mục do panel quản lý, ví dụ:

/www/dk_project/dk_app/ghost/<domain>/

Đây chỉ là ví dụ.

Hãy lấy đường dẫn thật từ:

  • Enter installation directory
  • Compose project
  • Nút mở thư mục của aaPanel

Sau đó SSH:

cd <AA_PANEL_COMPOSE_DIR>

6. Compose mẫu cho một website Ghost

File thực tế do aaPanel sinh có thể có các biến khác nhau. Ví dụ dưới đây là mẫu để hiểu cấu trúc:

services:
  ghost:
    image: ghost:6-alpine
    restart: unless-stopped

    ports:
      - "127.0.0.1:${HOST_PORT}:2368"

    environment:
      NODE_ENV: production

      url: https://${DOMAIN}

      database__client: mysql
      database__connection__host: ${MYSQL_HOST}
      database__connection__port: ${MYSQL_PORT}
      database__connection__user: ${MYSQL_USER}
      database__connection__password: ${MYSQL_PASSWORD}
      database__connection__database: ${MYSQL_DATABASE}

    volumes:
      - ./content:/var/lib/ghost/content

    networks:
      - mysql_network

networks:
  mysql_network:
    external: true
    name: <MYSQL_DOCKER_NETWORK>

Nếu file hiện tại của aaPanel đang dùng:

WEB_HTTP_PORT
MYSQL_HOST_IP
MYSQL_DATABASE
HOST_IP

thì giữ nguyên tên biến của file đó. Không đổi tên biến chỉ vì snippet minh họa dùng tên khác.


7. Ví dụ hoàn chỉnh: triển khai website thứ hai domain.com

Giả sử:

Domain:       domain.com
Host port:    2369
Ghost port:   2368

MySQL host:   mysql_aq
MySQL port:   3306
Database:     name_db
User:         user_db
Password:     <PASSWORD>

Compose có thể có dạng:

services:
  ghost:
    image: ghost:6-alpine
    restart: unless-stopped

    ports:
      - "127.0.0.1:2369:2368"

    environment:
      NODE_ENV: production
      url: https://domain.com

      database__client: mysql
      database__connection__host: mysql_aq
      database__connection__port: 3306
      database__connection__user: user_db
      database__connection__password: <PASSWORD>
      database__connection__database: name_db

    volumes:
      - ./content:/var/lib/ghost/content

    networks:
      - mysql_network

networks:
  mysql_network:
    external: true
    name: <MYSQL_DOCKER_NETWORK>
Password DB thật không nên đưa trực tiếp vào bài viết hoặc commit vào Git public.

8. Volume /var/lib/ghost/content là bắt buộc phải hiểu

Ghost lưu phần content quan trọng trong:

/var/lib/ghost/content

Ví dụ:

volumes:
  - ./content:/var/lib/ghost/content

Phần này bảo vệ những dữ liệu như:

  • ảnh
  • theme
  • content được lưu trong content directory

Database vẫn cần backup riêng.

Backup volume không thay thế database dump.

9. Kiểm tra Compose trước khi chạy

Sau khi tạo/chỉnh project:

cd <AA_PANEL_COMPOSE_DIR>

docker compose config
docker compose ps
docker compose logs --tail=100 <ghost_service>

docker compose config

Giúp phát hiện:

  • YAML sai
  • biến environment bị rỗng
  • cấu hình Compose không hợp lệ

docker compose ps

Cho biết:

  • service nào đang chạy
  • container nào đang chạy
  • port nào đã map
  • trạng thái container

Kiểm tra port

docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

Bạn cần thấy đại loại:

127.0.0.1:2369->2368/tcp

10. Khởi động Ghost

Nếu Compose đã đúng:

docker compose up -d

Kiểm tra:

docker compose ps

Xem log:

docker compose logs --tail=200 <ghost_service>

Checkpoint

Ghost cần:

  • container đang Up
  • không có lỗi MySQL
  • port host đúng
  • content đã mount

11. Test Ghost ngay trên VPS trước khi cấu hình domain

Đây là checkpoint quan trọng nhất.

Ví dụ host port là 2369:

curl -I http://127.0.0.1:2369/

Nếu Ghost phản hồi, hãy tiếp tục sang aaPanel.

Nếu không phản hồi:

docker compose logs --tail=200 <ghost_service>

Nếu curl thất bại

Đừng sửa Nginx ngay.

Kiểm tra theo thứ tự:

1. Container có chạy không?
2. Port có map đúng không?
3. Ghost có start thành công không?
4. Ghost có kết nối MySQL không?
5. Host port có listen không?

Checkpoint

✅ curl http://127.0.0.1:2369/ có response
→ Sang bước aaPanel Proxy

❌ Connection refused / timeout
→ Sửa Docker/Ghost trước

12. Kiểm tra public URL bên trong Ghost

Dùng service name thực tế:

docker compose exec <ghost_service> env | grep -E '^(url|NODE_ENV)='

Mong muốn:

url=https://domain.com
NODE_ENV=production

Không sửa config.production.json trong container chỉ vì file mặc định còn hiển thị localhost.

Environment variables có ưu tiên cao hơn file config. Cấu hình persistent nên đặt trong Compose/aaPanel để không mất sau recreate.


13. Tạo website trong aaPanel

Trong aaPanel:

Website

Tạo website cho:

domain.com

Hoặc dùng:

Website → Proxy

tùy phiên bản aaPanel.


14. Tạo Reverse Proxy

Target:

http://127.0.0.1:2369

Rất quan trọng

Đúng:

http://127.0.0.1:2369

Sai:

http://127.0.0.1:2368

2368 là port bên trong container.

Cũng không nên dùng:

http://172.18.0.3

vì IP container có thể thay đổi.

Không dùng:

http://ghost:2368

trong Nginx host, vì tên service chỉ được Docker DNS phân giải bên trong Docker network.


15. Header reverse proxy

Nếu aaPanel đã tự tạo proxy và header cần thiết, không tạo thêm location / thứ hai.

Trong config hiện có, kiểm tra các header tương đương:

location / {
    proxy_pass http://127.0.0.1:<HOST_PORT>;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

HostX-Forwarded-Host phải giữ hostname public.

X-Forwarded-Proto phải cho Ghost biết request ban đầu là HTTPS.

Sau khi chỉnh:

sudo nginx -t
sudo systemctl reload nginx

Checkpoint

✅ nginx -t: syntax is ok
✅ nginx reload thành công
→ Sang SSL

❌ nginx -t lỗi
→ Sửa config Nginx trước

16. Cấp SSL Let's Encrypt

Trong aaPanel:

Website → domain.com → SSL

Chọn:

Let's Encrypt

Cấp certificate cho domain.

DNS phải trỏ đúng VPS.

Sau đó kiểm tra:

curl -I https://domain.com/
curl -I https://domain.com/ghost/

Mở trình duyệt:

https://domain.com/ghost/

17. Nếu Ghost redirect về localhost hoặc domain cũ

Kiểm tra:

docker compose exec <ghost_service> env | grep -E '^(url|NODE_ENV)='

Mong muốn:

url=https://domain.com
NODE_ENV=production

Không dùng:

http://localhost:2368

cho production.


18. Sửa lỗi Request made from incorrect origin

Lỗi này thường liên quan đến sự không nhất quán giữa:

  • public URL của Ghost
  • Host
  • X-Forwarded-Host
  • X-Forwarded-Proto

Kiểm tra:

cd <AA_PANEL_COMPOSE_DIR>

docker compose config
docker compose exec <ghost_service> env | grep -E '^(url|NODE_ENV)='
sudo nginx -t

Kết quả:

url=https://domain.com
NODE_ENV=production

Sau đó kiểm tra proxy config.

Nếu header bị ghi đè thành:

localhost
IP container
http

hãy sửa lại.

Sau đó:

sudo nginx -t
sudo systemctl reload nginx

Thử:

https://domain.com/ghost/

và hard refresh/private window.

Nếu /ghost/ mở bình thường, không cần sửa file config nội bộ trong container chỉ để đổi localhost.

19. Sửa lỗi ECONNREFUSED ...:443IMAGE_SIZE_URL

Đây là nhánh lỗi khác.

Website có thể:

  • truy cập bình thường
  • /ghost/ hoạt động

nhưng Ghost lại tự gọi public domain từ bên trong container.

Ví dụ log:

ECONNREFUSED 172.18.0.3:443
IMAGE_SIZE_URL

Điều đó có thể xảy ra khi DNS trong container trỏ domain về chính IP Ghost container, trong khi HTTPS thực tế do Nginx trên host lắng nghe.

19.1. Kiểm tra DNS từ bên trong Ghost

docker compose exec <ghost_service> getent hosts mikka.io.vn

Lấy IP Ghost container:

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <ghost_container>

Chỉ kết luận là hairpin DNS khi:

IP DNS trả về == IP Ghost container

và log cho thấy kết nối tới:

<ghost-container-ip>:443

20. Dùng host-gateway khi đúng điều kiện

Trong service Ghost:

extra_hosts:
  - "mikka.io.vn:host-gateway"

Hoặc:

extra_hosts:
  - "${DOMAIN}:host-gateway"

Sau đó:

docker compose config
docker compose up -d --force-recreate

Kiểm tra:

docker compose exec <ghost_service> getent hosts mikka.io.vn

Xem log:

docker compose logs --tail=200 <ghost_service> | grep -Ei 'ECONNREFUSED|IMAGE_SIZE_URL|ERROR'

host-gateway map hostname tới gateway của Docker host. Đây là mapping trong container, không thay thế DNS public.

Không copy cứng 172.17.0.1. Gateway thực tế có thể khác theo network.

Nếu vẫn lỗi, kiểm tra:

  • host port Ghost
  • firewall
  • Nginx
  • khả năng hairpin routing

21. ActivityPub và webhook là lỗi riêng

Bạn có thể gặp:

Could not get webhook secret for ActivityPub
SyntaxError: Unexpected token '<', "<!DOCTYPE ..." is not valid JSON

Điều đó có nghĩa một request đang mong JSON nhưng nhận HTML.

Nếu:

https://domain.com/
https://domain.com/ghost/

vẫn hoạt động thì không nên kết luận ngay reverse proxy bị hỏng.

Nếu bạn không dùng ActivityPub self-hosted, hãy xử lý theo đúng tính năng và phiên bản Ghost đang chạy.

Không chữa lỗi này bằng cách đổi ngẫu nhiên:

proxy_pass
headers
Docker IP

22. Chạy nhiều Ghost trên cùng một VPS

Đây là một trong những lợi ích lớn của mô hình host-port riêng.

Ví dụ:

Ghost site 1
domain1.com
127.0.0.1:2369 → Ghost :2368

Ghost site 2
domain2.com
127.0.0.1:2370 → Ghost :2368

Ghost site 3
domain3.com
127.0.0.1:2371 → Ghost :2368

Nginx:

domain1.com → 127.0.0.1:2369
domain2.com → 127.0.0.1:2370
domain3.com → 127.0.0.1:2371

Mỗi site nên có:

  • Compose project riêng
  • content volume riêng
  • database riêng
  • credential riêng
  • host port riêng
  • domain riêng

Sơ đồ:

                       Nginx :443
                           │
          ┌────────────────┼────────────────┐
          ▼                ▼                ▼
       :2369             :2370            :2371
          │                │                │
       Ghost 1           Ghost 2          Ghost 3
          │                │                │
         DB1              DB2              DB3
Không cần dùng các port Ghost khác nhau bên trong container. Mỗi container Ghost vẫn có thể listen 2368; chỉ cần host port khác nhau.

23. Backup trước rebuild, uninstall và nâng cấp

Ghost có hai nhóm dữ liệu chính cần bảo vệ:

1. /var/lib/ghost/content
2. MySQL database

Trước khi:

  • Rebuild
  • Uninstall
  • đổi database credential
  • đổi volume
  • nâng version

hãy backup trước.

Một lỗi rất dễ mắc

Đổi:

MYSQL_PASSWORD=...

trong .env

không tự đổi password thật của user trong MySQL.

Nếu password trong Compose và password thật trong MySQL không đồng bộ, Ghost sẽ mất kết nối database.


24. Nâng Ghost an toàn

Nên:

  1. Backup content.
  2. Backup database.
  3. Đọc release notes.
  4. Pin version đã kiểm thử.
  5. Nâng từng minor version có kiểm soát.
  6. Kiểm tra log sau nâng cấp.

Ví dụ:

docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 <ghost_service>

Không nên sử dụng latest một cách mù quáng cho production nếu chưa kiểm thử.

Không dùng docker compose down -v nếu chưa hiểu rõ volume và dữ liệu nào sẽ bị xóa.

25. Checklist nghiệm thu production

[ ] DNS A/AAAA trỏ đúng VPS
[ ] TCP 80/443 mở
[ ] Docker Compose project chạy trong aaPanel
[ ] Ghost content được mount persistent
[ ] MySQL 8 và credential hoạt động
[ ] Ghost tham gia đúng Docker network của MySQL
[ ] Host port Ghost chỉ bind 127.0.0.1
[ ] docker compose config không còn biến rỗng
[ ] docker compose ps cho thấy Ghost đang chạy
[ ] curl http://127.0.0.1:<HOST_PORT>/ trả response
[ ] aaPanel Proxy target đúng 127.0.0.1:<HOST_PORT>
[ ] Host/X-Forwarded-Host/X-Forwarded-Proto đúng
[ ] nginx -t thành công
[ ] SSL Let's Encrypt hoạt động
[ ] https://domain.com/ hoạt động
[ ] https://domain.com/ghost/ hoạt động
[ ] Ghost url dùng https://domain.com
[ ] Chỉ thêm extra_hosts khi getent hosts xác nhận hairpin DNS
[ ] Có backup content
[ ] Có backup database
[ ] SMTP được cấu hình trước khi dùng member/newsletter

26. Troubleshooting nhanh

LỗiNguyên nhân thường gặpKiểm tra trước
Connection refused127.0.0.1:<HOST_PORT>Ghost/container chưa chạy hoặc port mapping saidocker compose ps
502 Bad GatewayNginx proxy sai host port hoặc Ghost chưa listencurl 127.0.0.1:<HOST_PORT>
Request made from incorrect originurl hoặc forwarded headers saiurl, Host, X-Forwarded-*
ECONNREFUSED ...:443Ghost tự gọi domain nhưng DNS trỏ vào containergetent hosts domain.com
IMAGE_SIZE_URLGhost không truy cập được public URL theo expected routeDNS từ bên trong container
Redirect về localhosturl production saidocker compose exec ... env
DB access deniedcredential không khớp MySQL thậtkiểm tra user/password/database
Unknown databasetên DB sai hoặc chưa tạokiểm tra MYSQL_DATABASE
ActivityPub trả HTML thay vì JSONwebhook/endpoint trả response không đúngkiểm tra ActivityPub/webhook riêng
Mất ảnh/theme sau recreatevolume content chưa persistentkiểm tra /var/lib/ghost/content

27. Câu hỏi thường gặp

Có bắt buộc cài Ghost vào /opt/ghost-aapanel không?

Không.

Đó chỉ là một đường dẫn minh họa cho Docker Compose thuần.

Khi dùng aaPanel, hãy dùng đường dẫn mà aaPanel hiển thị.


Có nên mở host port Ghost ra Internet không?

Không cần trong mô hình aaPanel Nginx cùng VPS.

Nên:

127.0.0.1:<HOST_PORT>

Sau đó public website qua:

443

Có phải lúc nào cũng cần extra_hosts không?

Không.

Chỉ thêm khi:

  1. domain public của Ghost bị resolve về chính IP container
  2. log cho thấy Ghost tự kết nối tới container-ip:443

Nếu DNS từ container bình thường thì không thêm workaround.


Có được dùng IP container cố định không?

Không nên.

Docker cấp IP container động; sau recreate IP có thể thay đổi.

Trong cùng Compose network:

service name → container

Để container gọi host:

host-gateway

khi đúng trường hợp.


Có nên chạy Ghost official Caddy stack trên aaPanel không?

Có thể, nhưng đó là kiến trúc khác.

Không nên để:

Caddy
+
Nginx aaPanel

cùng giữ domain/cổng 80/443.

Hãy chọn một lớp reverse proxy/TLS chính cho domain đó.


28. Kết luận

Cài Ghost production trên Ubuntu có aaPanel Docker không chỉ là chạy một image Ghost.

Cần tách đúng trách nhiệm:

aaPanel Docker/Compose
→ quản lý container và dữ liệu persistent

aaPanel Nginx
→ domain + SSL + reverse proxy

Ghost
→ public URL HTTPS + application

MySQL
→ database

Khi triển khai, hãy kiểm tra từ trong ra ngoài:

Docker + DB
    ↓
Host port
    ↓
aaPanel Proxy
    ↓
Nginx + SSL
    ↓
Public domain

Với lỗi:

ECONNREFUSED
IMAGE_SIZE_URL

hãy kiểm tra DNS bên trong container trước khi thêm extra_hosts.

Với lỗi:

Request made from incorrect origin

hãy kiểm tra url của Ghost và các forwarded headers trước.

Đây là cách giúp mô hình aaPanel + Docker không chỉ chạy được một website Ghost, mà còn có thể mở rộng thành nhiều Ghost/domain trên cùng một VPS với mỗi site có:

Compose riêng
Port riêng
Database riêng
Content riêng
Domain riêng

References

  1. Ghost Configuration
  2. Ghost Docker Official Image
  3. aaPanel Docker documentation
  4. Docker Compose Networking
  5. aaPanel Proxy documentation
  6. Ghost Docker installation