제작·이전

Ghost에서 Astro로 글과 이미지를 옮긴 실제 과정

Ghost 전체 백업, 콘텐츠 JSON 변환, Markdown 생성, 이미지 경로 정리, URL 검사와 Astro 서버 전환까지 minml에서 직접 진행한 과정을 설명합니다.

문서가 담긴 차콜 보관함과 라벤더 다리로 연결된 흰색 웹페이지를 표현한 3D 클레이 이미지

고스트를 떠나기로 결정한 뒤 가장 먼저 걱정한 것은 새로운 디자인이 아니었습니다. 기존 글과 이미지가 빠짐없이 옮겨지고, 사용하던 주소가 그대로 열리며, 문제가 생기면 이전 상태로 되돌릴 수 있어야 했습니다.

minml은 당시 Ghost 6.42.0으로 운영되고 있었습니다. 최종적으로는 글 18편과 고정 페이지 3개를 Astro 콘텐츠로 옮겼습니다. 이 글에서는 고스트를 떠난 이유보다 한 단계 더 들어가, 실제로 무엇을 백업하고 어떻게 변환하고 검사했는지 정리합니다.

이 과정이 모든 Ghost 사이트에 그대로 적용되는 범용 이전 도구는 아닙니다. 회원, 유료 구독, 뉴스레터를 사용하지 않는 개인 블로그였기 때문에 가능한 선택도 있었습니다. 다만 콘텐츠와 이미지를 가진 작은 Ghost 블로그를 정적 사이트로 옮길 때 무엇을 먼저 확인해야 하는지는 참고할 수 있습니다.

이전보다 백업을 먼저 했습니다

처음부터 Ghost를 삭제하거나 서버 설정을 바꾸지는 않았습니다. Astro 사이트를 만들다가 문제가 생겨도 기존 블로그로 돌아갈 수 있도록 별도의 전체 백업부터 만들었습니다.

당시 보관한 항목은 다음과 같습니다.

  • Ghost 데이터베이스 덤프
  • Ghost의 content 디렉터리와 운영 설정
  • Ghost CLI 관련 설정
  • Nginx 가상 호스트와 Ghost 서비스 설정
  • 각 백업 파일의 SHA-256 해시

파일이 존재하는 것만으로 백업이 끝났다고 보지는 않았습니다. 압축 파일의 내부 목록이 열리는지, 데이터베이스 압축 스트림이 정상적으로 풀리는지, SQL 덤프에 완료 표시가 있는지 확인했습니다. 당시 데이터베이스 덤프에서는 81개 테이블과 완료 표시를 확인했고, 서버에서 만든 해시와 로컬로 내려받은 파일의 해시도 비교했습니다.

이렇게 한 이유는 단순합니다. 복원할 수 있는지 확인하지 않은 백업은 실제 문제가 생겼을 때 믿기 어렵기 때문입니다. Ghost 공식 문서 역시 직접 운영하는 설치본을 백업할 때 콘텐츠뿐 아니라 이미지와 파일, 경로와 리디렉션 같은 구성도 함께 보관하도록 안내합니다.

글 데이터와 이미지 파일은 따로 다뤘습니다

Ghost에서 글을 내보내면 제목과 본문, 작성자, 태그, 발행일 같은 콘텐츠 정보는 JSON으로 다룰 수 있습니다. 하지만 본문이 이미지를 가리킨다고 해서 실제 이미지 파일까지 JSON 안에 들어가는 것은 아닙니다. 이미지와 업로드 파일은 Ghost의 콘텐츠 디렉터리에서 별도로 보관해야 합니다.

minml도 두 부분으로 나눴습니다.

  1. 글과 페이지, 메타데이터를 JSON으로 수집했습니다.
  2. 서버의 이미지 디렉터리를 별도로 복사했습니다.

마이그레이션용 JSON에는 총 21개 항목이 있었습니다. 글 18개와 소개·문의·개인정보처리방침 페이지 3개였습니다. 각 항목에서 다음 정보를 Astro 글 파일의 상단 정보로 옮겼습니다.

  • 제목과 기존 주소
  • 설명과 요약
  • 최초 발행일과 수정일
  • 작성자와 태그
  • 대표 이미지와 대체 텍스트
  • 검색 및 소셜 공유용 메타데이터

이미지 원본 폴더에는 여러 크기와 형식으로 생성된 파일까지 포함해 163개가 있었습니다. 글마다 이미지를 많이 사용해서 생긴 수가 아니라, Ghost가 화면 크기에 맞춰 사용할 변형 파일을 함께 만들어두었기 때문이었습니다. 원본 백업에서는 이 구조를 그대로 보존하고, 실제 Astro 사이트에는 글이 참조하는 경로를 기준으로 필요한 파일을 연결했습니다.

HTML 본문을 Markdown으로 변환했습니다

Astro에서는 글 한 편을 하나의 Markdown 파일로 관리하기로 했습니다. 따라서 JSON에 들어 있던 HTML 본문을 Markdown으로 변환하는 작은 스크립트를 만들었습니다.

변환 결과는 대략 다음과 같은 구조입니다.

---
title: "글 제목"
slug: "기존-글-주소"
publishedAt: "기존 발행일"
featureImage: "/content/images/이미지.png"
---

여기부터 변환된 본문이 들어갑니다.

단순히 HTML 태그를 모두 제거하지는 않았습니다. 일반적인 제목, 문단, 목록과 링크는 Markdown으로 바꾸되 표, iframe, 접기 영역, Ghost의 HTML 카드와 북마크 카드는 원래 HTML을 유지하도록 했습니다. 자동 변환 과정에서 표현할 수 없는 요소까지 억지로 Markdown으로 바꾸면 본문 일부가 사라질 수 있기 때문입니다.

이미지 주소도 함께 정리했습니다. 기존 본문에 https://minml.kr/content/images/...처럼 전체 도메인이 들어 있으면 /content/images/... 형태의 내부 경로로 바꿨습니다. 이렇게 하면 같은 도메인을 계속 사용하면서도 로컬 미리보기와 실제 서버에서 동일한 이미지 구조를 사용할 수 있습니다.

이 단계에서 알게 된 점은 마이그레이션이 복사와 붙여넣기만으로 끝나는 작업이 아니라는 것이었습니다. 본문과 메타데이터, 이미지 파일은 서로 연결되어 있지만 저장되는 방식은 다릅니다. 셋을 나눠 옮긴 뒤 다시 연결해야 했습니다.

기존 글 주소를 그대로 사용했습니다

이전 전 Ghost 글 주소가 /ghost-feature-image-resize/였다면 Astro에서도 같은 주소가 열리게 했습니다. 변환할 때 기존 slug를 버리지 않고 Markdown 파일에 그대로 저장했고, Astro가 이 값을 페이지 주소로 사용하도록 구성했습니다.

주소를 유지한 이유는 기존 링크를 깨뜨리지 않기 위해서였습니다. 검색 결과나 다른 글에 남아 있는 링크가 새로운 주소로 바뀌면 방문자는 404 페이지를 보게 됩니다. 꼭 주소를 바꿔야 하는 글만 별도의 리디렉션 대상으로 두고, 나머지는 원래 주소를 유지했습니다.

Astro 콘텐츠 컬렉션에는 제목, 주소, 날짜와 같은 필드의 형식을 검사하는 스키마도 만들었습니다. 필수 정보가 빠지거나 날짜 형식이 잘못되면 사이트를 만드는 과정에서 발견할 수 있습니다. 데이터베이스의 필드를 Markdown 상단 정보와 콘텐츠 스키마로 옮긴 셈입니다.

변환 개수보다 실제 결과를 검사했습니다

스크립트가 “18개 글을 변환했다”고 출력하는 것만으로는 충분하지 않았습니다. 파일은 만들어졌어도 페이지 주소나 이미지가 깨질 수 있기 때문입니다. 그래서 Astro를 빌드한 뒤 생성된 결과물을 별도의 검사 스크립트로 확인했습니다.

초기 이전 당시 확인한 항목은 다음과 같습니다.

  • 이전된 글 18편과 고정 페이지 3개가 모두 존재하는가
  • 각 글의 기존 주소에 HTML 파일이 생성됐는가
  • 대표 이미지와 본문 이미지가 실제 파일을 가리키는가
  • 내부 링크가 존재하는 페이지로 연결되는가
  • Ghost에서 사용하던 임시 URL 문자열이 남아 있지 않은가
  • RSS와 사이트맵, 작성자 페이지가 생성됐는가

당시 빌드에서는 HTML 파일 28개, 내부 경로 32개, 로컬 이미지 대상 20개를 확인했습니다. 숫자가 맞는지만 센 것이 아니라 각 경로가 실제 파일로 이어지는지 검사했습니다. 이후 콘텐츠를 합치거나 비공개로 전환하면서 공개 글 개수는 달라졌지만, 현재도 빌드할 때 같은 방식으로 공개 글과 비공개 글, 리디렉션, 이미지와 내부 링크를 확인합니다.

새 사이트를 확인한 뒤 Ghost를 정리했습니다

로컬에서 정상적으로 보인다고 바로 기존 Ghost를 지우지는 않았습니다. Astro 결과물을 서버의 새로운 릴리스 폴더에 올리고, 도메인이 새 정적 파일을 가리키게 한 뒤 실제 운영 환경을 확인했습니다.

확인 범위에는 다음이 포함됐습니다.

  • 홈페이지와 개별 글이 HTTPS로 열리는가
  • 기존 글 주소가 유지되는가
  • 이미지와 CSS가 정상적으로 표시되는가
  • RSS, 사이트맵과 robots.txt가 열리는가
  • 같은 서버의 다른 웹사이트에 영향이 없는가

다른 사이트는 처음부터 작업 범위에서 제외했습니다. 같은 서버에 있다는 이유로 관련 없는 설정까지 함께 정리하지 않았습니다.

운영 환경 검사가 끝난 후에야 Ghost 애플리케이션과 전용 데이터베이스를 정리했습니다. 그 직전에도 Ghost 전체 백업, Astro 소스, 실제 배포 파일을 한 묶음으로 다시 보관했습니다. 현재 Ghost를 복구하려면 다시 설치하는 과정이 필요하지만, 콘텐츠와 데이터베이스, 당시 설정은 로컬 백업에 남아 있습니다.

직접 옮겨보니 중요했던 순서

이번 작업에서 가장 중요했던 것은 변환 프로그램보다 순서였습니다.

  1. 복원 가능한 전체 백업을 만든다.
  2. 글 데이터와 이미지 파일을 따로 확보한다.
  3. 기존 주소를 유지하며 콘텐츠 형식을 변환한다.
  4. 로컬에서 화면과 빌드를 확인한다.
  5. 생성된 주소, 링크와 이미지 파일을 자동 검사한다.
  6. 새 사이트를 먼저 운영 환경에 올린다.
  7. 실제 도메인에서 확인한 뒤에만 기존 서비스를 정리한다.

이 순서를 지키면 각 단계에서 문제가 생겨도 이전 단계로 돌아갈 수 있습니다. 반대로 기존 서비스를 먼저 지우고 변환을 시작하면 작은 누락도 복구 작업이 됩니다.

AI가 코드를 만들었지만 판단까지 대신한 것은 아닙니다

JSON 구조를 읽고 Markdown을 만드는 코드, 이미지 주소를 바꾸는 규칙, 빌드 결과를 검사하는 스크립트는 AI의 도움으로 작성했습니다. 명령의 의미나 서버 구성을 전부 외운 상태에서 시작한 작업은 아니었습니다.

그렇다고 “Ghost를 Astro로 바꿔줘”라는 한 문장으로 모든 작업이 끝난 것은 아닙니다. 어떤 데이터가 반드시 남아야 하는지, 주소를 유지할지, 다른 사이트를 작업 대상에서 제외할지, 언제 Ghost를 삭제해도 되는지는 제가 결정해야 했습니다. AI가 속도를 높여준 것은 맞지만, 백업과 검증 기준까지 없애준 것은 아니었습니다.

이전 이후 글을 발행하는 방식은 관리자 화면에서 파일 기반 작업으로 바뀌었습니다. 현재 사용하는 작성·미리보기·빌드·배포 과정은 CMS 없이 Astro 블로그를 운영하는 과정에 따로 정리했습니다.

한 달 뒤 다시 검사해도 이전 결과가 남아 있었습니다

마이그레이션 직후에는 새 화면이 열리는지에 시선이 쏠리기 쉽습니다. 그래서 2026년 9월 11일에 글을 추가하고 사이트 구조를 바꾼 뒤에도 전체 검사를 다시 실행했습니다. 당시 사이트에는 이전 후 새로 쓴 글을 포함해 공개 글 23편, 공개하지 않는 초안 10편과 고정 페이지 3개가 있었습니다. 통합한 과거 주소 6개도 계속 새 주소로 이동했습니다.

다시 확인한 항목 결과
생성된 HTML 파일 40개
검사한 내부 경로 38개
검사한 로컬 이미지 24개
통합한 과거 주소 6개

이 숫자는 처음 옮긴 콘텐츠 수와 일치하는지를 한 번만 확인한 기록이 아닙니다. 새 글과 주제 페이지가 늘어난 뒤에도 전체 콘텐츠를 다시 빌드하고, 각 링크가 실제 생성 파일이나 명시한 리디렉션으로 이어지는지 확인한 결과입니다. 당시 공개 글은 세 개의 주제 메뉴와 전체 글 목록에 함께 노출됐지만, 기존 글 주소는 바꾸지 않았습니다.

이후 구조를 개편하면서도 콘텐츠 Markdown과 이미지 파일은 그대로 두고 목록을 만드는 코드만 바꿀 수 있었습니다. 글, 표시 방식과 공개 주소를 분리해 옮겨둔 덕분입니다. 이전 당시 디자인보다 원본·주소·검사를 먼저 정한 순서가 장기적으로도 유효했다는 것을 확인했습니다.

지금 다시 옮겨도 같은 방식으로 할 것 같습니다

다시 마이그레이션하더라도 먼저 전체 백업을 만들고, 콘텐츠와 이미지를 분리해 확보한 뒤, 주소와 파일을 자동으로 검사할 것입니다. 디자인은 그다음입니다. 화면은 나중에도 수정할 수 있지만 사라진 원본과 끊어진 주소는 뒤늦게 복구하기 어렵습니다.

Ghost의 내보내기 기능이 부족해서 Astro로 떠난 것은 아닙니다. Ghost는 콘텐츠 JSON 내보내기를 제공하고, 직접 운영하는 설치본의 전체 백업 방법도 공식 문서에 안내하고 있습니다. 제 경우에는 내보낸 데이터를 계속 실행되는 CMS로 복원하는 대신, 제가 이해하기 쉬운 Markdown과 정적 HTML 구조로 바꿔 보관하고 싶었습니다.

결과적으로 마이그레이션은 “블로그 프로그램을 교체한 작업”보다는 “콘텐츠의 저장 형식과 공개 방식을 다시 정한 작업”에 가까웠습니다. 글은 Markdown으로, 이미지는 일반 파일로, 공개 페이지는 미리 만들어진 HTML로 남았습니다. 지금의 저에게는 이 구조가 Ghost를 계속 운영하는 것보다 단순합니다.

참고한 공식 문서