[{"data":1,"prerenderedAt":2650},["ShallowReactive",2],{"blog-post-\u002Fko\u002Fminio-guide":3,"blog-related-source":2391},{"id":4,"title":5,"body":6,"description":17,"extension":2379,"meta":2380,"navigation":348,"path":2386,"seo":2387,"sitemap":2388,"stem":2389,"__hash__":2390},"ko\u002Fko\u002Fminio-guide.md","MinIO 완벽 가이드: 설치부터 Node.js 연동까지",{"type":7,"value":8,"toc":2341},"minimark",[9,14,18,22,31,34,41,44,51,54,61,64,71,74,78,81,85,197,201,217,221,228,233,246,250,278,281,285,288,292,308,312,425,429,627,631,784,788,905,909,1104,1108,1215,1219,1378,1382,2040,2044,2048,2051,2128,2132,2135,2208,2212,2215,2317,2320,2323,2337],[10,11,13],"h2",{"id":12},"minio란-무엇인가","MinIO란 무엇인가?",[15,16,17],"p",{},"MinIO는 오픈소스 기반의 고성능 객체 스토리지 서버입니다. AWS S3와 호환되는 API를 제공하여 클라우드 네이티브 애플리케이션을 위한 완벽한 스토리지 솔루션으로 자리잡고 있습니다.",[10,19,21],{"id":20},"왜-minio를-사용해야-할까","왜 MinIO를 사용해야 할까?",[23,24,26,27],"h3",{"id":25},"_1-aws-s3-호환성","1. ",[28,29,30],"strong",{},"AWS S3 호환성",[15,32,33],{},"MinIO는 S3 API와 완벽하게 호환됩니다. 즉, 기존 S3 클라이언트 라이브러리를 그대로 사용할 수 있어 학습 곡선이 낮고 마이그레이션이 쉽습니다.",[23,35,37,38],{"id":36},"_2-비용-절감","2. ",[28,39,40],{},"비용 절감",[15,42,43],{},"클라우드 스토리지 비용이 부담되시나요? MinIO는 온프레미스나 프라이빗 클라우드에서 직접 운영할 수 있어 장기적으로 비용을 크게 절감할 수 있습니다.",[23,45,47,48],{"id":46},"_3-개발-환경-구축의-용이성","3. ",[28,49,50],{},"개발 환경 구축의 용이성",[15,52,53],{},"로컬 개발 환경에서 S3와 동일한 API를 사용하며 테스트할 수 있습니다. 실제 AWS 계정 없이도 개발과 테스트가 가능합니다.",[23,55,57,58],{"id":56},"_4-높은-성능","4. ",[28,59,60],{},"높은 성능",[15,62,63],{},"Go 언어로 작성되어 가볍고 빠르며, 멀티 테넌시와 고가용성을 지원합니다.",[23,65,67,68],{"id":66},"_5-쿠버네티스-친화적","5. ",[28,69,70],{},"쿠버네티스 친화적",[15,72,73],{},"클라우드 네이티브 아키텍처에 최적화되어 있어 컨테이너 환경에서 쉽게 배포하고 관리할 수 있습니다.",[10,75,77],{"id":76},"docker로-minio-설치하기","Docker로 MinIO 설치하기",[15,79,80],{},"Docker를 이용하면 MinIO를 몇 분 안에 실행할 수 있습니다.",[23,82,84],{"id":83},"기본-설치","기본 설치",[86,87,92],"pre",{"className":88,"code":89,"language":90,"meta":91,"style":91},"language-bash shiki shiki-themes github-light github-dark","docker run -d \\\n  -p 9000:9000 \\\n  -p 9001:9001 \\\n  --name minio \\\n  -e MINIO_ROOT_USER=minioadmin \\\n  -e MINIO_ROOT_PASSWORD=minioadmin \\\n  -v ~\u002Fminio\u002Fdata:\u002Fdata \\\n  minio\u002Fminio server \u002Fdata --console-address \":9001\"\n","bash","",[93,94,95,115,126,136,147,158,168,179],"code",{"__ignoreMap":91},[96,97,100,104,108,112],"span",{"class":98,"line":99},"line",1,[96,101,103],{"class":102},"sScJk","docker",[96,105,107],{"class":106},"sZZnC"," run",[96,109,111],{"class":110},"sj4cs"," -d",[96,113,114],{"class":110}," \\\n",[96,116,118,121,124],{"class":98,"line":117},2,[96,119,120],{"class":110},"  -p",[96,122,123],{"class":106}," 9000:9000",[96,125,114],{"class":110},[96,127,129,131,134],{"class":98,"line":128},3,[96,130,120],{"class":110},[96,132,133],{"class":106}," 9001:9001",[96,135,114],{"class":110},[96,137,139,142,145],{"class":98,"line":138},4,[96,140,141],{"class":110},"  --name",[96,143,144],{"class":106}," minio",[96,146,114],{"class":110},[96,148,150,153,156],{"class":98,"line":149},5,[96,151,152],{"class":110},"  -e",[96,154,155],{"class":106}," MINIO_ROOT_USER=minioadmin",[96,157,114],{"class":110},[96,159,161,163,166],{"class":98,"line":160},6,[96,162,152],{"class":110},[96,164,165],{"class":106}," MINIO_ROOT_PASSWORD=minioadmin",[96,167,114],{"class":110},[96,169,171,174,177],{"class":98,"line":170},7,[96,172,173],{"class":110},"  -v",[96,175,176],{"class":106}," ~\u002Fminio\u002Fdata:\u002Fdata",[96,178,114],{"class":110},[96,180,182,185,188,191,194],{"class":98,"line":181},8,[96,183,184],{"class":106},"  minio\u002Fminio",[96,186,187],{"class":106}," server",[96,189,190],{"class":106}," \u002Fdata",[96,192,193],{"class":110}," --console-address",[96,195,196],{"class":106}," \":9001\"\n",[23,198,200],{"id":199},"포트-설명","포트 설명",[202,203,204,211],"ul",{},[205,206,207,210],"li",{},[28,208,209],{},"9000",": MinIO API 서버 포트 (S3 호환 API)",[205,212,213,216],{},[28,214,215],{},"9001",": MinIO Console UI 포트 (웹 관리 콘솔)",[23,218,220],{"id":219},"minio-console-접속","MinIO Console 접속",[15,222,223,224,227],{},"브라우저에서 ",[93,225,226],{},"http:\u002F\u002Flocalhost:9001","로 접속합니다.",[15,229,230],{},[28,231,232],{},"로그인 정보:",[202,234,235,241],{},[205,236,237,238],{},"Username: ",[93,239,240],{},"minioadmin",[205,242,243,244],{},"Password: ",[93,245,240],{},[23,247,249],{"id":248},"console에서-버킷-생성하기","Console에서 버킷 생성하기",[251,252,253,260,266,273],"ol",{},[205,254,255,256,259],{},"왼쪽 메뉴에서 ",[28,257,258],{},"Buckets"," 클릭",[205,261,262,265],{},[28,263,264],{},"Create Bucket"," 버튼 클릭",[205,267,268,269,272],{},"버킷 이름 입력 (예: ",[93,270,271],{},"test-bucket",")",[205,274,275,265],{},[28,276,277],{},"Create",[15,279,280],{},"이제 MinIO가 정상적으로 작동하는 것을 확인했습니다!",[10,282,284],{"id":283},"nodejs에서-minio-사용하기","Node.js에서 MinIO 사용하기",[15,286,287],{},"실제 애플리케이션에서 MinIO를 어떻게 사용하는지 알아보겠습니다.",[23,289,291],{"id":290},"_1-패키지-설치","1. 패키지 설치",[86,293,295],{"className":88,"code":294,"language":90,"meta":91,"style":91},"npm install minio\n",[93,296,297],{"__ignoreMap":91},[96,298,299,302,305],{"class":98,"line":99},[96,300,301],{"class":102},"npm",[96,303,304],{"class":106}," install",[96,306,307],{"class":106}," minio\n",[23,309,311],{"id":310},"_2-minio-클라이언트-초기화","2. MinIO 클라이언트 초기화",[86,313,317],{"className":314,"code":315,"language":316,"meta":91,"style":91},"language-javascript shiki shiki-themes github-light github-dark","const Minio = require('minio');\n\nconst minioClient = new Minio.Client({\n  endPoint: 'localhost',\n  port: 9000,\n  useSSL: false,\n  accessKey: 'minioadmin',\n  secretKey: 'minioadmin'\n});\n","javascript",[93,318,319,344,350,371,382,391,401,411,419],{"__ignoreMap":91},[96,320,321,325,328,331,334,338,341],{"class":98,"line":99},[96,322,324],{"class":323},"szBVR","const",[96,326,327],{"class":110}," Minio",[96,329,330],{"class":323}," =",[96,332,333],{"class":102}," require",[96,335,337],{"class":336},"sVt8B","(",[96,339,340],{"class":106},"'minio'",[96,342,343],{"class":336},");\n",[96,345,346],{"class":98,"line":117},[96,347,349],{"emptyLinePlaceholder":348},true,"\n",[96,351,352,354,357,359,362,365,368],{"class":98,"line":128},[96,353,324],{"class":323},[96,355,356],{"class":110}," minioClient",[96,358,330],{"class":323},[96,360,361],{"class":323}," new",[96,363,364],{"class":336}," Minio.",[96,366,367],{"class":102},"Client",[96,369,370],{"class":336},"({\n",[96,372,373,376,379],{"class":98,"line":138},[96,374,375],{"class":336},"  endPoint: ",[96,377,378],{"class":106},"'localhost'",[96,380,381],{"class":336},",\n",[96,383,384,387,389],{"class":98,"line":149},[96,385,386],{"class":336},"  port: ",[96,388,209],{"class":110},[96,390,381],{"class":336},[96,392,393,396,399],{"class":98,"line":160},[96,394,395],{"class":336},"  useSSL: ",[96,397,398],{"class":110},"false",[96,400,381],{"class":336},[96,402,403,406,409],{"class":98,"line":170},[96,404,405],{"class":336},"  accessKey: ",[96,407,408],{"class":106},"'minioadmin'",[96,410,381],{"class":336},[96,412,413,416],{"class":98,"line":181},[96,414,415],{"class":336},"  secretKey: ",[96,417,418],{"class":106},"'minioadmin'\n",[96,420,422],{"class":98,"line":421},9,[96,423,424],{"class":336},"});\n",[23,426,428],{"id":427},"_3-버킷-생성","3. 버킷 생성",[86,430,432],{"className":314,"code":431,"language":316,"meta":91,"style":91},"async function createBucket(bucketName) {\n  try {\n    const exists = await minioClient.bucketExists(bucketName);\n    \n    if (exists) {\n      console.log(`버킷 '${bucketName}'이 이미 존재합니다.`);\n    } else {\n      await minioClient.makeBucket(bucketName, 'us-east-1');\n      console.log(`버킷 '${bucketName}'이 생성되었습니다.`);\n    }\n  } catch (err) {\n    console.error('버킷 생성 중 오류:', err);\n  }\n}\n\ncreateBucket('my-bucket');\n",[93,433,434,454,462,484,489,497,517,527,545,562,568,580,597,603,609,614],{"__ignoreMap":91},[96,435,436,439,442,445,447,451],{"class":98,"line":99},[96,437,438],{"class":323},"async",[96,440,441],{"class":323}," function",[96,443,444],{"class":102}," createBucket",[96,446,337],{"class":336},[96,448,450],{"class":449},"s4XuR","bucketName",[96,452,453],{"class":336},") {\n",[96,455,456,459],{"class":98,"line":117},[96,457,458],{"class":323},"  try",[96,460,461],{"class":336}," {\n",[96,463,464,467,470,472,475,478,481],{"class":98,"line":128},[96,465,466],{"class":323},"    const",[96,468,469],{"class":110}," exists",[96,471,330],{"class":323},[96,473,474],{"class":323}," await",[96,476,477],{"class":336}," minioClient.",[96,479,480],{"class":102},"bucketExists",[96,482,483],{"class":336},"(bucketName);\n",[96,485,486],{"class":98,"line":138},[96,487,488],{"class":336},"    \n",[96,490,491,494],{"class":98,"line":149},[96,492,493],{"class":323},"    if",[96,495,496],{"class":336}," (exists) {\n",[96,498,499,502,505,507,510,512,515],{"class":98,"line":160},[96,500,501],{"class":336},"      console.",[96,503,504],{"class":102},"log",[96,506,337],{"class":336},[96,508,509],{"class":106},"`버킷 '${",[96,511,450],{"class":336},[96,513,514],{"class":106},"}'이 이미 존재합니다.`",[96,516,343],{"class":336},[96,518,519,522,525],{"class":98,"line":170},[96,520,521],{"class":336},"    } ",[96,523,524],{"class":323},"else",[96,526,461],{"class":336},[96,528,529,532,534,537,540,543],{"class":98,"line":181},[96,530,531],{"class":323},"      await",[96,533,477],{"class":336},[96,535,536],{"class":102},"makeBucket",[96,538,539],{"class":336},"(bucketName, ",[96,541,542],{"class":106},"'us-east-1'",[96,544,343],{"class":336},[96,546,547,549,551,553,555,557,560],{"class":98,"line":421},[96,548,501],{"class":336},[96,550,504],{"class":102},[96,552,337],{"class":336},[96,554,509],{"class":106},[96,556,450],{"class":336},[96,558,559],{"class":106},"}'이 생성되었습니다.`",[96,561,343],{"class":336},[96,563,565],{"class":98,"line":564},10,[96,566,567],{"class":336},"    }\n",[96,569,571,574,577],{"class":98,"line":570},11,[96,572,573],{"class":336},"  } ",[96,575,576],{"class":323},"catch",[96,578,579],{"class":336}," (err) {\n",[96,581,583,586,589,591,594],{"class":98,"line":582},12,[96,584,585],{"class":336},"    console.",[96,587,588],{"class":102},"error",[96,590,337],{"class":336},[96,592,593],{"class":106},"'버킷 생성 중 오류:'",[96,595,596],{"class":336},", err);\n",[96,598,600],{"class":98,"line":599},13,[96,601,602],{"class":336},"  }\n",[96,604,606],{"class":98,"line":605},14,[96,607,608],{"class":336},"}\n",[96,610,612],{"class":98,"line":611},15,[96,613,349],{"emptyLinePlaceholder":348},[96,615,617,620,622,625],{"class":98,"line":616},16,[96,618,619],{"class":102},"createBucket",[96,621,337],{"class":336},[96,623,624],{"class":106},"'my-bucket'",[96,626,343],{"class":336},[23,628,630],{"id":629},"_4-파일-업로드","4. 파일 업로드",[86,632,634],{"className":314,"code":633,"language":316,"meta":91,"style":91},"async function uploadFile(bucketName, objectName, filePath) {\n  try {\n    const metaData = {\n      'Content-Type': 'application\u002Foctet-stream'\n    };\n    \n    await minioClient.fPutObject(bucketName, objectName, filePath, metaData);\n    console.log(`파일 '${objectName}'이 업로드되었습니다.`);\n  } catch (err) {\n    console.error('파일 업로드 중 오류:', err);\n  }\n}\n\nuploadFile('my-bucket', 'test.txt', '.\u002Ftest.txt');\n",[93,635,636,662,668,679,690,695,699,712,730,738,751,755,759,763],{"__ignoreMap":91},[96,637,638,640,642,645,647,649,652,655,657,660],{"class":98,"line":99},[96,639,438],{"class":323},[96,641,441],{"class":323},[96,643,644],{"class":102}," uploadFile",[96,646,337],{"class":336},[96,648,450],{"class":449},[96,650,651],{"class":336},", ",[96,653,654],{"class":449},"objectName",[96,656,651],{"class":336},[96,658,659],{"class":449},"filePath",[96,661,453],{"class":336},[96,663,664,666],{"class":98,"line":117},[96,665,458],{"class":323},[96,667,461],{"class":336},[96,669,670,672,675,677],{"class":98,"line":128},[96,671,466],{"class":323},[96,673,674],{"class":110}," metaData",[96,676,330],{"class":323},[96,678,461],{"class":336},[96,680,681,684,687],{"class":98,"line":138},[96,682,683],{"class":106},"      'Content-Type'",[96,685,686],{"class":336},": ",[96,688,689],{"class":106},"'application\u002Foctet-stream'\n",[96,691,692],{"class":98,"line":149},[96,693,694],{"class":336},"    };\n",[96,696,697],{"class":98,"line":160},[96,698,488],{"class":336},[96,700,701,704,706,709],{"class":98,"line":170},[96,702,703],{"class":323},"    await",[96,705,477],{"class":336},[96,707,708],{"class":102},"fPutObject",[96,710,711],{"class":336},"(bucketName, objectName, filePath, metaData);\n",[96,713,714,716,718,720,723,725,728],{"class":98,"line":181},[96,715,585],{"class":336},[96,717,504],{"class":102},[96,719,337],{"class":336},[96,721,722],{"class":106},"`파일 '${",[96,724,654],{"class":336},[96,726,727],{"class":106},"}'이 업로드되었습니다.`",[96,729,343],{"class":336},[96,731,732,734,736],{"class":98,"line":421},[96,733,573],{"class":336},[96,735,576],{"class":323},[96,737,579],{"class":336},[96,739,740,742,744,746,749],{"class":98,"line":564},[96,741,585],{"class":336},[96,743,588],{"class":102},[96,745,337],{"class":336},[96,747,748],{"class":106},"'파일 업로드 중 오류:'",[96,750,596],{"class":336},[96,752,753],{"class":98,"line":570},[96,754,602],{"class":336},[96,756,757],{"class":98,"line":582},[96,758,608],{"class":336},[96,760,761],{"class":98,"line":599},[96,762,349],{"emptyLinePlaceholder":348},[96,764,765,768,770,772,774,777,779,782],{"class":98,"line":605},[96,766,767],{"class":102},"uploadFile",[96,769,337],{"class":336},[96,771,624],{"class":106},[96,773,651],{"class":336},[96,775,776],{"class":106},"'test.txt'",[96,778,651],{"class":336},[96,780,781],{"class":106},"'.\u002Ftest.txt'",[96,783,343],{"class":336},[23,785,787],{"id":786},"_5-파일-다운로드","5. 파일 다운로드",[86,789,791],{"className":314,"code":790,"language":316,"meta":91,"style":91},"async function downloadFile(bucketName, objectName, downloadPath) {\n  try {\n    await minioClient.fGetObject(bucketName, objectName, downloadPath);\n    console.log(`파일 '${objectName}'이 다운로드되었습니다.`);\n  } catch (err) {\n    console.error('파일 다운로드 중 오류:', err);\n  }\n}\n\ndownloadFile('my-bucket', 'test.txt', '.\u002Fdownloaded-test.txt');\n",[93,792,793,817,823,835,852,860,873,877,881,885],{"__ignoreMap":91},[96,794,795,797,799,802,804,806,808,810,812,815],{"class":98,"line":99},[96,796,438],{"class":323},[96,798,441],{"class":323},[96,800,801],{"class":102}," downloadFile",[96,803,337],{"class":336},[96,805,450],{"class":449},[96,807,651],{"class":336},[96,809,654],{"class":449},[96,811,651],{"class":336},[96,813,814],{"class":449},"downloadPath",[96,816,453],{"class":336},[96,818,819,821],{"class":98,"line":117},[96,820,458],{"class":323},[96,822,461],{"class":336},[96,824,825,827,829,832],{"class":98,"line":128},[96,826,703],{"class":323},[96,828,477],{"class":336},[96,830,831],{"class":102},"fGetObject",[96,833,834],{"class":336},"(bucketName, objectName, downloadPath);\n",[96,836,837,839,841,843,845,847,850],{"class":98,"line":138},[96,838,585],{"class":336},[96,840,504],{"class":102},[96,842,337],{"class":336},[96,844,722],{"class":106},[96,846,654],{"class":336},[96,848,849],{"class":106},"}'이 다운로드되었습니다.`",[96,851,343],{"class":336},[96,853,854,856,858],{"class":98,"line":149},[96,855,573],{"class":336},[96,857,576],{"class":323},[96,859,579],{"class":336},[96,861,862,864,866,868,871],{"class":98,"line":160},[96,863,585],{"class":336},[96,865,588],{"class":102},[96,867,337],{"class":336},[96,869,870],{"class":106},"'파일 다운로드 중 오류:'",[96,872,596],{"class":336},[96,874,875],{"class":98,"line":170},[96,876,602],{"class":336},[96,878,879],{"class":98,"line":181},[96,880,608],{"class":336},[96,882,883],{"class":98,"line":421},[96,884,349],{"emptyLinePlaceholder":348},[96,886,887,890,892,894,896,898,900,903],{"class":98,"line":564},[96,888,889],{"class":102},"downloadFile",[96,891,337],{"class":336},[96,893,624],{"class":106},[96,895,651],{"class":336},[96,897,776],{"class":106},[96,899,651],{"class":336},[96,901,902],{"class":106},"'.\u002Fdownloaded-test.txt'",[96,904,343],{"class":336},[23,906,908],{"id":907},"_6-파일-목록-조회","6. 파일 목록 조회",[86,910,912],{"className":314,"code":911,"language":316,"meta":91,"style":91},"async function listObjects(bucketName) {\n  try {\n    const stream = minioClient.listObjects(bucketName, '', true);\n    \n    stream.on('data', (obj) => {\n      console.log('파일:', obj.name, '크기:', obj.size);\n    });\n    \n    stream.on('error', (err) => {\n      console.error('목록 조회 중 오류:', err);\n    });\n  } catch (err) {\n    console.error('오류:', err);\n  }\n}\n\nlistObjects('my-bucket');\n",[93,913,914,929,935,961,965,992,1012,1017,1021,1043,1056,1060,1068,1081,1085,1089,1093],{"__ignoreMap":91},[96,915,916,918,920,923,925,927],{"class":98,"line":99},[96,917,438],{"class":323},[96,919,441],{"class":323},[96,921,922],{"class":102}," listObjects",[96,924,337],{"class":336},[96,926,450],{"class":449},[96,928,453],{"class":336},[96,930,931,933],{"class":98,"line":117},[96,932,458],{"class":323},[96,934,461],{"class":336},[96,936,937,939,942,944,946,949,951,954,956,959],{"class":98,"line":128},[96,938,466],{"class":323},[96,940,941],{"class":110}," stream",[96,943,330],{"class":323},[96,945,477],{"class":336},[96,947,948],{"class":102},"listObjects",[96,950,539],{"class":336},[96,952,953],{"class":106},"''",[96,955,651],{"class":336},[96,957,958],{"class":110},"true",[96,960,343],{"class":336},[96,962,963],{"class":98,"line":138},[96,964,488],{"class":336},[96,966,967,970,973,975,978,981,984,987,990],{"class":98,"line":149},[96,968,969],{"class":336},"    stream.",[96,971,972],{"class":102},"on",[96,974,337],{"class":336},[96,976,977],{"class":106},"'data'",[96,979,980],{"class":336},", (",[96,982,983],{"class":449},"obj",[96,985,986],{"class":336},") ",[96,988,989],{"class":323},"=>",[96,991,461],{"class":336},[96,993,994,996,998,1000,1003,1006,1009],{"class":98,"line":160},[96,995,501],{"class":336},[96,997,504],{"class":102},[96,999,337],{"class":336},[96,1001,1002],{"class":106},"'파일:'",[96,1004,1005],{"class":336},", obj.name, ",[96,1007,1008],{"class":106},"'크기:'",[96,1010,1011],{"class":336},", obj.size);\n",[96,1013,1014],{"class":98,"line":170},[96,1015,1016],{"class":336},"    });\n",[96,1018,1019],{"class":98,"line":181},[96,1020,488],{"class":336},[96,1022,1023,1025,1027,1029,1032,1034,1037,1039,1041],{"class":98,"line":421},[96,1024,969],{"class":336},[96,1026,972],{"class":102},[96,1028,337],{"class":336},[96,1030,1031],{"class":106},"'error'",[96,1033,980],{"class":336},[96,1035,1036],{"class":449},"err",[96,1038,986],{"class":336},[96,1040,989],{"class":323},[96,1042,461],{"class":336},[96,1044,1045,1047,1049,1051,1054],{"class":98,"line":564},[96,1046,501],{"class":336},[96,1048,588],{"class":102},[96,1050,337],{"class":336},[96,1052,1053],{"class":106},"'목록 조회 중 오류:'",[96,1055,596],{"class":336},[96,1057,1058],{"class":98,"line":570},[96,1059,1016],{"class":336},[96,1061,1062,1064,1066],{"class":98,"line":582},[96,1063,573],{"class":336},[96,1065,576],{"class":323},[96,1067,579],{"class":336},[96,1069,1070,1072,1074,1076,1079],{"class":98,"line":599},[96,1071,585],{"class":336},[96,1073,588],{"class":102},[96,1075,337],{"class":336},[96,1077,1078],{"class":106},"'오류:'",[96,1080,596],{"class":336},[96,1082,1083],{"class":98,"line":605},[96,1084,602],{"class":336},[96,1086,1087],{"class":98,"line":611},[96,1088,608],{"class":336},[96,1090,1091],{"class":98,"line":616},[96,1092,349],{"emptyLinePlaceholder":348},[96,1094,1096,1098,1100,1102],{"class":98,"line":1095},17,[96,1097,948],{"class":102},[96,1099,337],{"class":336},[96,1101,624],{"class":106},[96,1103,343],{"class":336},[23,1105,1107],{"id":1106},"_7-파일-삭제","7. 파일 삭제",[86,1109,1111],{"className":314,"code":1110,"language":316,"meta":91,"style":91},"async function deleteFile(bucketName, objectName) {\n  try {\n    await minioClient.removeObject(bucketName, objectName);\n    console.log(`파일 '${objectName}'이 삭제되었습니다.`);\n  } catch (err) {\n    console.error('파일 삭제 중 오류:', err);\n  }\n}\n\ndeleteFile('my-bucket', 'test.txt');\n",[93,1112,1113,1132,1138,1150,1167,1175,1188,1192,1196,1200],{"__ignoreMap":91},[96,1114,1115,1117,1119,1122,1124,1126,1128,1130],{"class":98,"line":99},[96,1116,438],{"class":323},[96,1118,441],{"class":323},[96,1120,1121],{"class":102}," deleteFile",[96,1123,337],{"class":336},[96,1125,450],{"class":449},[96,1127,651],{"class":336},[96,1129,654],{"class":449},[96,1131,453],{"class":336},[96,1133,1134,1136],{"class":98,"line":117},[96,1135,458],{"class":323},[96,1137,461],{"class":336},[96,1139,1140,1142,1144,1147],{"class":98,"line":128},[96,1141,703],{"class":323},[96,1143,477],{"class":336},[96,1145,1146],{"class":102},"removeObject",[96,1148,1149],{"class":336},"(bucketName, objectName);\n",[96,1151,1152,1154,1156,1158,1160,1162,1165],{"class":98,"line":138},[96,1153,585],{"class":336},[96,1155,504],{"class":102},[96,1157,337],{"class":336},[96,1159,722],{"class":106},[96,1161,654],{"class":336},[96,1163,1164],{"class":106},"}'이 삭제되었습니다.`",[96,1166,343],{"class":336},[96,1168,1169,1171,1173],{"class":98,"line":149},[96,1170,573],{"class":336},[96,1172,576],{"class":323},[96,1174,579],{"class":336},[96,1176,1177,1179,1181,1183,1186],{"class":98,"line":160},[96,1178,585],{"class":336},[96,1180,588],{"class":102},[96,1182,337],{"class":336},[96,1184,1185],{"class":106},"'파일 삭제 중 오류:'",[96,1187,596],{"class":336},[96,1189,1190],{"class":98,"line":170},[96,1191,602],{"class":336},[96,1193,1194],{"class":98,"line":181},[96,1195,608],{"class":336},[96,1197,1198],{"class":98,"line":421},[96,1199,349],{"emptyLinePlaceholder":348},[96,1201,1202,1205,1207,1209,1211,1213],{"class":98,"line":564},[96,1203,1204],{"class":102},"deleteFile",[96,1206,337],{"class":336},[96,1208,624],{"class":106},[96,1210,651],{"class":336},[96,1212,776],{"class":106},[96,1214,343],{"class":336},[23,1216,1218],{"id":1217},"_8-presigned-url-생성-임시-접근-링크","8. Presigned URL 생성 (임시 접근 링크)",[86,1220,1222],{"className":314,"code":1221,"language":316,"meta":91,"style":91},"async function getPresignedUrl(bucketName, objectName, expirySeconds = 3600) {\n  try {\n    const url = await minioClient.presignedGetObject(\n      bucketName, \n      objectName, \n      expirySeconds\n    );\n    console.log('다운로드 URL:', url);\n    return url;\n  } catch (err) {\n    console.error('URL 생성 중 오류:', err);\n  }\n}\n\ngetPresignedUrl('my-bucket', 'test.txt', 7200); \u002F\u002F 2시간 유효\n",[93,1223,1224,1253,1259,1278,1283,1288,1293,1298,1312,1320,1328,1341,1345,1349,1353],{"__ignoreMap":91},[96,1225,1226,1228,1230,1233,1235,1237,1239,1241,1243,1246,1248,1251],{"class":98,"line":99},[96,1227,438],{"class":323},[96,1229,441],{"class":323},[96,1231,1232],{"class":102}," getPresignedUrl",[96,1234,337],{"class":336},[96,1236,450],{"class":449},[96,1238,651],{"class":336},[96,1240,654],{"class":449},[96,1242,651],{"class":336},[96,1244,1245],{"class":449},"expirySeconds",[96,1247,330],{"class":323},[96,1249,1250],{"class":110}," 3600",[96,1252,453],{"class":336},[96,1254,1255,1257],{"class":98,"line":117},[96,1256,458],{"class":323},[96,1258,461],{"class":336},[96,1260,1261,1263,1266,1268,1270,1272,1275],{"class":98,"line":128},[96,1262,466],{"class":323},[96,1264,1265],{"class":110}," url",[96,1267,330],{"class":323},[96,1269,474],{"class":323},[96,1271,477],{"class":336},[96,1273,1274],{"class":102},"presignedGetObject",[96,1276,1277],{"class":336},"(\n",[96,1279,1280],{"class":98,"line":138},[96,1281,1282],{"class":336},"      bucketName, \n",[96,1284,1285],{"class":98,"line":149},[96,1286,1287],{"class":336},"      objectName, \n",[96,1289,1290],{"class":98,"line":160},[96,1291,1292],{"class":336},"      expirySeconds\n",[96,1294,1295],{"class":98,"line":170},[96,1296,1297],{"class":336},"    );\n",[96,1299,1300,1302,1304,1306,1309],{"class":98,"line":181},[96,1301,585],{"class":336},[96,1303,504],{"class":102},[96,1305,337],{"class":336},[96,1307,1308],{"class":106},"'다운로드 URL:'",[96,1310,1311],{"class":336},", url);\n",[96,1313,1314,1317],{"class":98,"line":421},[96,1315,1316],{"class":323},"    return",[96,1318,1319],{"class":336}," url;\n",[96,1321,1322,1324,1326],{"class":98,"line":564},[96,1323,573],{"class":336},[96,1325,576],{"class":323},[96,1327,579],{"class":336},[96,1329,1330,1332,1334,1336,1339],{"class":98,"line":570},[96,1331,585],{"class":336},[96,1333,588],{"class":102},[96,1335,337],{"class":336},[96,1337,1338],{"class":106},"'URL 생성 중 오류:'",[96,1340,596],{"class":336},[96,1342,1343],{"class":98,"line":582},[96,1344,602],{"class":336},[96,1346,1347],{"class":98,"line":599},[96,1348,608],{"class":336},[96,1350,1351],{"class":98,"line":605},[96,1352,349],{"emptyLinePlaceholder":348},[96,1354,1355,1358,1360,1362,1364,1366,1368,1371,1374],{"class":98,"line":611},[96,1356,1357],{"class":102},"getPresignedUrl",[96,1359,337],{"class":336},[96,1361,624],{"class":106},[96,1363,651],{"class":336},[96,1365,776],{"class":106},[96,1367,651],{"class":336},[96,1369,1370],{"class":110},"7200",[96,1372,1373],{"class":336},"); ",[96,1375,1377],{"class":1376},"sJ8bj","\u002F\u002F 2시간 유효\n",[10,1379,1381],{"id":1380},"실전-활용-예제-expressjs-파일-업로드","실전 활용 예제: Express.js 파일 업로드",[86,1383,1385],{"className":314,"code":1384,"language":316,"meta":91,"style":91},"const express = require('express');\nconst multer = require('multer');\nconst Minio = require('minio');\nconst fs = require('fs');\n\nconst app = express();\nconst upload = multer({ dest: 'uploads\u002F' });\n\nconst minioClient = new Minio.Client({\n  endPoint: 'localhost',\n  port: 9000,\n  useSSL: false,\n  accessKey: 'minioadmin',\n  secretKey: 'minioadmin'\n});\n\nconst BUCKET_NAME = 'uploads';\n\n\u002F\u002F 버킷 초기화\n(async () => {\n  const exists = await minioClient.bucketExists(BUCKET_NAME);\n  if (!exists) {\n    await minioClient.makeBucket(BUCKET_NAME, 'us-east-1');\n  }\n})();\n\n\u002F\u002F 파일 업로드 엔드포인트\napp.post('\u002Fupload', upload.single('file'), async (req, res) => {\n  try {\n    const file = req.file;\n    const objectName = `${Date.now()}-${file.originalname}`;\n    \n    await minioClient.fPutObject(\n      BUCKET_NAME,\n      objectName,\n      file.path,\n      { 'Content-Type': file.mimetype }\n    );\n    \n    \u002F\u002F 임시 파일 삭제\n    fs.unlinkSync(file.path);\n    \n    \u002F\u002F Presigned URL 생성\n    const url = await minioClient.presignedGetObject(BUCKET_NAME, objectName);\n    \n    res.json({\n      success: true,\n      message: '파일이 업로드되었습니다.',\n      filename: objectName,\n      url: url\n    });\n  } catch (err) {\n    res.status(500).json({ success: false, error: err.message });\n  }\n});\n\napp.listen(3000, () => {\n  console.log('서버가 3000번 포트에서 실행 중입니다.');\n});\n",[93,1386,1387,1405,1423,1439,1457,1461,1475,1495,1499,1515,1523,1531,1539,1547,1553,1557,1561,1576,1581,1587,1601,1624,1639,1658,1663,1669,1674,1680,1726,1733,1746,1787,1792,1803,1811,1817,1823,1835,1840,1845,1851,1863,1868,1874,1896,1901,1912,1922,1933,1939,1945,1950,1959,1985,1990,1995,2000,2020,2035],{"__ignoreMap":91},[96,1388,1389,1391,1394,1396,1398,1400,1403],{"class":98,"line":99},[96,1390,324],{"class":323},[96,1392,1393],{"class":110}," express",[96,1395,330],{"class":323},[96,1397,333],{"class":102},[96,1399,337],{"class":336},[96,1401,1402],{"class":106},"'express'",[96,1404,343],{"class":336},[96,1406,1407,1409,1412,1414,1416,1418,1421],{"class":98,"line":117},[96,1408,324],{"class":323},[96,1410,1411],{"class":110}," multer",[96,1413,330],{"class":323},[96,1415,333],{"class":102},[96,1417,337],{"class":336},[96,1419,1420],{"class":106},"'multer'",[96,1422,343],{"class":336},[96,1424,1425,1427,1429,1431,1433,1435,1437],{"class":98,"line":128},[96,1426,324],{"class":323},[96,1428,327],{"class":110},[96,1430,330],{"class":323},[96,1432,333],{"class":102},[96,1434,337],{"class":336},[96,1436,340],{"class":106},[96,1438,343],{"class":336},[96,1440,1441,1443,1446,1448,1450,1452,1455],{"class":98,"line":138},[96,1442,324],{"class":323},[96,1444,1445],{"class":110}," fs",[96,1447,330],{"class":323},[96,1449,333],{"class":102},[96,1451,337],{"class":336},[96,1453,1454],{"class":106},"'fs'",[96,1456,343],{"class":336},[96,1458,1459],{"class":98,"line":149},[96,1460,349],{"emptyLinePlaceholder":348},[96,1462,1463,1465,1468,1470,1472],{"class":98,"line":160},[96,1464,324],{"class":323},[96,1466,1467],{"class":110}," app",[96,1469,330],{"class":323},[96,1471,1393],{"class":102},[96,1473,1474],{"class":336},"();\n",[96,1476,1477,1479,1482,1484,1486,1489,1492],{"class":98,"line":170},[96,1478,324],{"class":323},[96,1480,1481],{"class":110}," upload",[96,1483,330],{"class":323},[96,1485,1411],{"class":102},[96,1487,1488],{"class":336},"({ dest: ",[96,1490,1491],{"class":106},"'uploads\u002F'",[96,1493,1494],{"class":336}," });\n",[96,1496,1497],{"class":98,"line":181},[96,1498,349],{"emptyLinePlaceholder":348},[96,1500,1501,1503,1505,1507,1509,1511,1513],{"class":98,"line":421},[96,1502,324],{"class":323},[96,1504,356],{"class":110},[96,1506,330],{"class":323},[96,1508,361],{"class":323},[96,1510,364],{"class":336},[96,1512,367],{"class":102},[96,1514,370],{"class":336},[96,1516,1517,1519,1521],{"class":98,"line":564},[96,1518,375],{"class":336},[96,1520,378],{"class":106},[96,1522,381],{"class":336},[96,1524,1525,1527,1529],{"class":98,"line":570},[96,1526,386],{"class":336},[96,1528,209],{"class":110},[96,1530,381],{"class":336},[96,1532,1533,1535,1537],{"class":98,"line":582},[96,1534,395],{"class":336},[96,1536,398],{"class":110},[96,1538,381],{"class":336},[96,1540,1541,1543,1545],{"class":98,"line":599},[96,1542,405],{"class":336},[96,1544,408],{"class":106},[96,1546,381],{"class":336},[96,1548,1549,1551],{"class":98,"line":605},[96,1550,415],{"class":336},[96,1552,418],{"class":106},[96,1554,1555],{"class":98,"line":611},[96,1556,424],{"class":336},[96,1558,1559],{"class":98,"line":616},[96,1560,349],{"emptyLinePlaceholder":348},[96,1562,1563,1565,1568,1570,1573],{"class":98,"line":1095},[96,1564,324],{"class":323},[96,1566,1567],{"class":110}," BUCKET_NAME",[96,1569,330],{"class":323},[96,1571,1572],{"class":106}," 'uploads'",[96,1574,1575],{"class":336},";\n",[96,1577,1579],{"class":98,"line":1578},18,[96,1580,349],{"emptyLinePlaceholder":348},[96,1582,1584],{"class":98,"line":1583},19,[96,1585,1586],{"class":1376},"\u002F\u002F 버킷 초기화\n",[96,1588,1590,1592,1594,1597,1599],{"class":98,"line":1589},20,[96,1591,337],{"class":336},[96,1593,438],{"class":323},[96,1595,1596],{"class":336}," () ",[96,1598,989],{"class":323},[96,1600,461],{"class":336},[96,1602,1604,1607,1609,1611,1613,1615,1617,1619,1622],{"class":98,"line":1603},21,[96,1605,1606],{"class":323},"  const",[96,1608,469],{"class":110},[96,1610,330],{"class":323},[96,1612,474],{"class":323},[96,1614,477],{"class":336},[96,1616,480],{"class":102},[96,1618,337],{"class":336},[96,1620,1621],{"class":110},"BUCKET_NAME",[96,1623,343],{"class":336},[96,1625,1627,1630,1633,1636],{"class":98,"line":1626},22,[96,1628,1629],{"class":323},"  if",[96,1631,1632],{"class":336}," (",[96,1634,1635],{"class":323},"!",[96,1637,1638],{"class":336},"exists) {\n",[96,1640,1642,1644,1646,1648,1650,1652,1654,1656],{"class":98,"line":1641},23,[96,1643,703],{"class":323},[96,1645,477],{"class":336},[96,1647,536],{"class":102},[96,1649,337],{"class":336},[96,1651,1621],{"class":110},[96,1653,651],{"class":336},[96,1655,542],{"class":106},[96,1657,343],{"class":336},[96,1659,1661],{"class":98,"line":1660},24,[96,1662,602],{"class":336},[96,1664,1666],{"class":98,"line":1665},25,[96,1667,1668],{"class":336},"})();\n",[96,1670,1672],{"class":98,"line":1671},26,[96,1673,349],{"emptyLinePlaceholder":348},[96,1675,1677],{"class":98,"line":1676},27,[96,1678,1679],{"class":1376},"\u002F\u002F 파일 업로드 엔드포인트\n",[96,1681,1683,1686,1689,1691,1694,1697,1700,1702,1705,1708,1710,1712,1715,1717,1720,1722,1724],{"class":98,"line":1682},28,[96,1684,1685],{"class":336},"app.",[96,1687,1688],{"class":102},"post",[96,1690,337],{"class":336},[96,1692,1693],{"class":106},"'\u002Fupload'",[96,1695,1696],{"class":336},", upload.",[96,1698,1699],{"class":102},"single",[96,1701,337],{"class":336},[96,1703,1704],{"class":106},"'file'",[96,1706,1707],{"class":336},"), ",[96,1709,438],{"class":323},[96,1711,1632],{"class":336},[96,1713,1714],{"class":449},"req",[96,1716,651],{"class":336},[96,1718,1719],{"class":449},"res",[96,1721,986],{"class":336},[96,1723,989],{"class":323},[96,1725,461],{"class":336},[96,1727,1729,1731],{"class":98,"line":1728},29,[96,1730,458],{"class":323},[96,1732,461],{"class":336},[96,1734,1736,1738,1741,1743],{"class":98,"line":1735},30,[96,1737,466],{"class":323},[96,1739,1740],{"class":110}," file",[96,1742,330],{"class":323},[96,1744,1745],{"class":336}," req.file;\n",[96,1747,1749,1751,1754,1756,1759,1762,1765,1768,1771,1774,1777,1779,1782,1785],{"class":98,"line":1748},31,[96,1750,466],{"class":323},[96,1752,1753],{"class":110}," objectName",[96,1755,330],{"class":323},[96,1757,1758],{"class":106}," `${",[96,1760,1761],{"class":336},"Date",[96,1763,1764],{"class":106},".",[96,1766,1767],{"class":102},"now",[96,1769,1770],{"class":106},"()",[96,1772,1773],{"class":106},"}-${",[96,1775,1776],{"class":336},"file",[96,1778,1764],{"class":106},[96,1780,1781],{"class":336},"originalname",[96,1783,1784],{"class":106},"}`",[96,1786,1575],{"class":336},[96,1788,1790],{"class":98,"line":1789},32,[96,1791,488],{"class":336},[96,1793,1795,1797,1799,1801],{"class":98,"line":1794},33,[96,1796,703],{"class":323},[96,1798,477],{"class":336},[96,1800,708],{"class":102},[96,1802,1277],{"class":336},[96,1804,1806,1809],{"class":98,"line":1805},34,[96,1807,1808],{"class":110},"      BUCKET_NAME",[96,1810,381],{"class":336},[96,1812,1814],{"class":98,"line":1813},35,[96,1815,1816],{"class":336},"      objectName,\n",[96,1818,1820],{"class":98,"line":1819},36,[96,1821,1822],{"class":336},"      file.path,\n",[96,1824,1826,1829,1832],{"class":98,"line":1825},37,[96,1827,1828],{"class":336},"      { ",[96,1830,1831],{"class":106},"'Content-Type'",[96,1833,1834],{"class":336},": file.mimetype }\n",[96,1836,1838],{"class":98,"line":1837},38,[96,1839,1297],{"class":336},[96,1841,1843],{"class":98,"line":1842},39,[96,1844,488],{"class":336},[96,1846,1848],{"class":98,"line":1847},40,[96,1849,1850],{"class":1376},"    \u002F\u002F 임시 파일 삭제\n",[96,1852,1854,1857,1860],{"class":98,"line":1853},41,[96,1855,1856],{"class":336},"    fs.",[96,1858,1859],{"class":102},"unlinkSync",[96,1861,1862],{"class":336},"(file.path);\n",[96,1864,1866],{"class":98,"line":1865},42,[96,1867,488],{"class":336},[96,1869,1871],{"class":98,"line":1870},43,[96,1872,1873],{"class":1376},"    \u002F\u002F Presigned URL 생성\n",[96,1875,1877,1879,1881,1883,1885,1887,1889,1891,1893],{"class":98,"line":1876},44,[96,1878,466],{"class":323},[96,1880,1265],{"class":110},[96,1882,330],{"class":323},[96,1884,474],{"class":323},[96,1886,477],{"class":336},[96,1888,1274],{"class":102},[96,1890,337],{"class":336},[96,1892,1621],{"class":110},[96,1894,1895],{"class":336},", objectName);\n",[96,1897,1899],{"class":98,"line":1898},45,[96,1900,488],{"class":336},[96,1902,1904,1907,1910],{"class":98,"line":1903},46,[96,1905,1906],{"class":336},"    res.",[96,1908,1909],{"class":102},"json",[96,1911,370],{"class":336},[96,1913,1915,1918,1920],{"class":98,"line":1914},47,[96,1916,1917],{"class":336},"      success: ",[96,1919,958],{"class":110},[96,1921,381],{"class":336},[96,1923,1925,1928,1931],{"class":98,"line":1924},48,[96,1926,1927],{"class":336},"      message: ",[96,1929,1930],{"class":106},"'파일이 업로드되었습니다.'",[96,1932,381],{"class":336},[96,1934,1936],{"class":98,"line":1935},49,[96,1937,1938],{"class":336},"      filename: objectName,\n",[96,1940,1942],{"class":98,"line":1941},50,[96,1943,1944],{"class":336},"      url: url\n",[96,1946,1948],{"class":98,"line":1947},51,[96,1949,1016],{"class":336},[96,1951,1953,1955,1957],{"class":98,"line":1952},52,[96,1954,573],{"class":336},[96,1956,576],{"class":323},[96,1958,579],{"class":336},[96,1960,1962,1964,1967,1969,1972,1975,1977,1980,1982],{"class":98,"line":1961},53,[96,1963,1906],{"class":336},[96,1965,1966],{"class":102},"status",[96,1968,337],{"class":336},[96,1970,1971],{"class":110},"500",[96,1973,1974],{"class":336},").",[96,1976,1909],{"class":102},[96,1978,1979],{"class":336},"({ success: ",[96,1981,398],{"class":110},[96,1983,1984],{"class":336},", error: err.message });\n",[96,1986,1988],{"class":98,"line":1987},54,[96,1989,602],{"class":336},[96,1991,1993],{"class":98,"line":1992},55,[96,1994,424],{"class":336},[96,1996,1998],{"class":98,"line":1997},56,[96,1999,349],{"emptyLinePlaceholder":348},[96,2001,2003,2005,2008,2010,2013,2016,2018],{"class":98,"line":2002},57,[96,2004,1685],{"class":336},[96,2006,2007],{"class":102},"listen",[96,2009,337],{"class":336},[96,2011,2012],{"class":110},"3000",[96,2014,2015],{"class":336},", () ",[96,2017,989],{"class":323},[96,2019,461],{"class":336},[96,2021,2023,2026,2028,2030,2033],{"class":98,"line":2022},58,[96,2024,2025],{"class":336},"  console.",[96,2027,504],{"class":102},[96,2029,337],{"class":336},[96,2031,2032],{"class":106},"'서버가 3000번 포트에서 실행 중입니다.'",[96,2034,343],{"class":336},[96,2036,2038],{"class":98,"line":2037},59,[96,2039,424],{"class":336},[10,2041,2043],{"id":2042},"프로덕션-환경-고려사항","프로덕션 환경 고려사항",[23,2045,2047],{"id":2046},"_1-보안-설정","1. 보안 설정",[15,2049,2050],{},"기본 계정 정보를 반드시 변경하세요.",[86,2052,2054],{"className":88,"code":2053,"language":90,"meta":91,"style":91},"docker run -d \\\n  -p 9000:9000 \\\n  -p 9001:9001 \\\n  --name minio \\\n  -e MINIO_ROOT_USER=your_secure_username \\\n  -e MINIO_ROOT_PASSWORD=your_secure_password_min_8_chars \\\n  -v ~\u002Fminio\u002Fdata:\u002Fdata \\\n  minio\u002Fminio server \u002Fdata --console-address \":9001\"\n",[93,2055,2056,2066,2074,2082,2090,2099,2108,2116],{"__ignoreMap":91},[96,2057,2058,2060,2062,2064],{"class":98,"line":99},[96,2059,103],{"class":102},[96,2061,107],{"class":106},[96,2063,111],{"class":110},[96,2065,114],{"class":110},[96,2067,2068,2070,2072],{"class":98,"line":117},[96,2069,120],{"class":110},[96,2071,123],{"class":106},[96,2073,114],{"class":110},[96,2075,2076,2078,2080],{"class":98,"line":128},[96,2077,120],{"class":110},[96,2079,133],{"class":106},[96,2081,114],{"class":110},[96,2083,2084,2086,2088],{"class":98,"line":138},[96,2085,141],{"class":110},[96,2087,144],{"class":106},[96,2089,114],{"class":110},[96,2091,2092,2094,2097],{"class":98,"line":149},[96,2093,152],{"class":110},[96,2095,2096],{"class":106}," MINIO_ROOT_USER=your_secure_username",[96,2098,114],{"class":110},[96,2100,2101,2103,2106],{"class":98,"line":160},[96,2102,152],{"class":110},[96,2104,2105],{"class":106}," MINIO_ROOT_PASSWORD=your_secure_password_min_8_chars",[96,2107,114],{"class":110},[96,2109,2110,2112,2114],{"class":98,"line":170},[96,2111,173],{"class":110},[96,2113,176],{"class":106},[96,2115,114],{"class":110},[96,2117,2118,2120,2122,2124,2126],{"class":98,"line":181},[96,2119,184],{"class":106},[96,2121,187],{"class":106},[96,2123,190],{"class":106},[96,2125,193],{"class":110},[96,2127,196],{"class":106},[23,2129,2131],{"id":2130},"_2-ssltls-설정","2. SSL\u002FTLS 설정",[15,2133,2134],{},"프로덕션에서는 반드시 HTTPS를 사용하세요.",[86,2136,2138],{"className":314,"code":2137,"language":316,"meta":91,"style":91},"const minioClient = new Minio.Client({\n  endPoint: 'minio.yourdomain.com',\n  port: 443,\n  useSSL: true,  \u002F\u002F HTTPS 사용\n  accessKey: process.env.MINIO_ACCESS_KEY,\n  secretKey: process.env.MINIO_SECRET_KEY\n});\n",[93,2139,2140,2156,2165,2174,2186,2196,2204],{"__ignoreMap":91},[96,2141,2142,2144,2146,2148,2150,2152,2154],{"class":98,"line":99},[96,2143,324],{"class":323},[96,2145,356],{"class":110},[96,2147,330],{"class":323},[96,2149,361],{"class":323},[96,2151,364],{"class":336},[96,2153,367],{"class":102},[96,2155,370],{"class":336},[96,2157,2158,2160,2163],{"class":98,"line":117},[96,2159,375],{"class":336},[96,2161,2162],{"class":106},"'minio.yourdomain.com'",[96,2164,381],{"class":336},[96,2166,2167,2169,2172],{"class":98,"line":128},[96,2168,386],{"class":336},[96,2170,2171],{"class":110},"443",[96,2173,381],{"class":336},[96,2175,2176,2178,2180,2183],{"class":98,"line":138},[96,2177,395],{"class":336},[96,2179,958],{"class":110},[96,2181,2182],{"class":336},",  ",[96,2184,2185],{"class":1376},"\u002F\u002F HTTPS 사용\n",[96,2187,2188,2191,2194],{"class":98,"line":149},[96,2189,2190],{"class":336},"  accessKey: process.env.",[96,2192,2193],{"class":110},"MINIO_ACCESS_KEY",[96,2195,381],{"class":336},[96,2197,2198,2201],{"class":98,"line":160},[96,2199,2200],{"class":336},"  secretKey: process.env.",[96,2202,2203],{"class":110},"MINIO_SECRET_KEY\n",[96,2205,2206],{"class":98,"line":170},[96,2207,424],{"class":336},[23,2209,2211],{"id":2210},"_3-환경변수-사용","3. 환경변수 사용",[15,2213,2214],{},"민감한 정보는 환경변수로 관리하세요.",[86,2216,2218],{"className":314,"code":2217,"language":316,"meta":91,"style":91},"require('dotenv').config();\n\nconst minioClient = new Minio.Client({\n  endPoint: process.env.MINIO_ENDPOINT,\n  port: parseInt(process.env.MINIO_PORT),\n  useSSL: process.env.MINIO_USE_SSL === 'true',\n  accessKey: process.env.MINIO_ACCESS_KEY,\n  secretKey: process.env.MINIO_SECRET_KEY\n});\n",[93,2219,2220,2237,2241,2257,2267,2283,2299,2307,2313],{"__ignoreMap":91},[96,2221,2222,2225,2227,2230,2232,2235],{"class":98,"line":99},[96,2223,2224],{"class":102},"require",[96,2226,337],{"class":336},[96,2228,2229],{"class":106},"'dotenv'",[96,2231,1974],{"class":336},[96,2233,2234],{"class":102},"config",[96,2236,1474],{"class":336},[96,2238,2239],{"class":98,"line":117},[96,2240,349],{"emptyLinePlaceholder":348},[96,2242,2243,2245,2247,2249,2251,2253,2255],{"class":98,"line":128},[96,2244,324],{"class":323},[96,2246,356],{"class":110},[96,2248,330],{"class":323},[96,2250,361],{"class":323},[96,2252,364],{"class":336},[96,2254,367],{"class":102},[96,2256,370],{"class":336},[96,2258,2259,2262,2265],{"class":98,"line":138},[96,2260,2261],{"class":336},"  endPoint: process.env.",[96,2263,2264],{"class":110},"MINIO_ENDPOINT",[96,2266,381],{"class":336},[96,2268,2269,2271,2274,2277,2280],{"class":98,"line":149},[96,2270,386],{"class":336},[96,2272,2273],{"class":102},"parseInt",[96,2275,2276],{"class":336},"(process.env.",[96,2278,2279],{"class":110},"MINIO_PORT",[96,2281,2282],{"class":336},"),\n",[96,2284,2285,2288,2291,2294,2297],{"class":98,"line":160},[96,2286,2287],{"class":336},"  useSSL: process.env.",[96,2289,2290],{"class":110},"MINIO_USE_SSL",[96,2292,2293],{"class":323}," ===",[96,2295,2296],{"class":106}," 'true'",[96,2298,381],{"class":336},[96,2300,2301,2303,2305],{"class":98,"line":170},[96,2302,2190],{"class":336},[96,2304,2193],{"class":110},[96,2306,381],{"class":336},[96,2308,2309,2311],{"class":98,"line":181},[96,2310,2200],{"class":336},[96,2312,2203],{"class":110},[96,2314,2315],{"class":98,"line":421},[96,2316,424],{"class":336},[10,2318,2319],{"id":2319},"마치며",[15,2321,2322],{},"MinIO는 S3 호환 객체 스토리지가 필요한 모든 프로젝트에 훌륭한 선택입니다. 특히 다음과 같은 경우에 적극 추천합니다:",[202,2324,2325,2328,2331,2334],{},[205,2326,2327],{},"로컬 개발 환경에서 S3를 시뮬레이션하고 싶을 때",[205,2329,2330],{},"클라우드 비용을 절감하고 싶을 때",[205,2332,2333],{},"데이터 주권이나 컴플라이언스 요구사항이 있을 때",[205,2335,2336],{},"멀티클라우드 전략을 구사하고 싶을 때",[2338,2339,2340],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":91,"searchDepth":117,"depth":117,"links":2342},[2343,2344,2356,2362,2372,2373,2378],{"id":12,"depth":117,"text":13},{"id":20,"depth":117,"text":21,"children":2345},[2346,2348,2350,2352,2354],{"id":25,"depth":128,"text":2347},"1. AWS S3 호환성",{"id":36,"depth":128,"text":2349},"2. 비용 절감",{"id":46,"depth":128,"text":2351},"3. 개발 환경 구축의 용이성",{"id":56,"depth":128,"text":2353},"4. 높은 성능",{"id":66,"depth":128,"text":2355},"5. 쿠버네티스 친화적",{"id":76,"depth":117,"text":77,"children":2357},[2358,2359,2360,2361],{"id":83,"depth":128,"text":84},{"id":199,"depth":128,"text":200},{"id":219,"depth":128,"text":220},{"id":248,"depth":128,"text":249},{"id":283,"depth":117,"text":284,"children":2363},[2364,2365,2366,2367,2368,2369,2370,2371],{"id":290,"depth":128,"text":291},{"id":310,"depth":128,"text":311},{"id":427,"depth":128,"text":428},{"id":629,"depth":128,"text":630},{"id":786,"depth":128,"text":787},{"id":907,"depth":128,"text":908},{"id":1106,"depth":128,"text":1107},{"id":1217,"depth":128,"text":1218},{"id":1380,"depth":117,"text":1381},{"id":2042,"depth":117,"text":2043,"children":2374},[2375,2376,2377],{"id":2046,"depth":128,"text":2047},{"id":2130,"depth":128,"text":2131},{"id":2210,"depth":128,"text":2211},{"id":2319,"depth":117,"text":2319},"md",{"date":2381,"field":2382,"tags":2383},"2025.11.23","backend",[2384,2385],"minio","nodejs","\u002Fko\u002Fminio-guide",{"title":5,"description":17},{"loc":2386},"ko\u002Fminio-guide","kokmY60Lo1IQpas72FHzOYMisJ1mo0FVBBk_pC5rNwQ",[2392,2403,2413,2421,2428,2435,2446,2454,2462,2474,2481,2490,2496,2503,2510,2517,2523,2532,2535,2542,2548,2555,2562,2570,2577,2584,2593,2600,2607,2614,2622,2629,2637,2643],{"path":2393,"title":2394,"description":2395,"meta":2396},"\u002Fko\u002Fadsense-ads-txt-and-seo-indexing-bug","애드센스 ads.txt 경고를 받고 파헤치다가 찾은 진짜 색인 버그","구글 애드센스가 ads.txt 문제를 알려왔는데, 확인해보니 파일은 멀쩡했다. 대신 서치콘솔 색인 수가 적었던 진짜 원인을 Nuxt Content 슬러그 불일치 버그에서 찾은 과정을 정리한다.",{"date":2397,"field":2398,"tags":2399},"2026.09.17","blog",[2400,2401,2402],"seo","adsense","nuxt-content",{"path":2404,"title":2405,"description":2406,"meta":2407},"\u002Fko\u002Fcss-layouts","CSS Flexbox 완전 정복 가이드","Flexbox를 활용한 반응형 레이아웃 구현 방법과 실전 예제. 수직 정렬, 네비게이션 바, 카드 레이아웃까지 완벽 정리!",{"date":2408,"image":2409,"field":2410,"tags":2411},"2025.01.25","css.jpg","frontend",[2412],"css",{"path":2414,"title":2415,"description":2416,"meta":2417},"\u002Fko\u002Fcss-login-form","로그인","로그인 UI를 그려보자",{"date":2418,"field":2410,"tags":2419},"2025.03.29",[2420,2412],"html",{"path":2422,"title":2423,"description":2424,"meta":2425},"\u002Fko\u002Fcsv-parser-cleaning","복잡한 CSV 데이터를 안전하게 파싱하는 방법","JavaScript에서 큰따옴표, 줄바꿈, 쉼표 등 복잡한 CSV 데이터를 안전하게 객체로 변환하는 전체 과정 설명",{"date":2426,"field":2410,"tags":2427},"2025.06.19",[316],{"path":2429,"title":2430,"description":2431,"meta":2432},"\u002Fko\u002Fdebounce-guide","debounce와 throttle이란??","반복되는 함수 호출을 제어하는 기술인 디바운스(debounce)와 쓰로틀(throttle)을 알아보자.",{"date":2433,"field":2410,"tags":2434},"2025.03.26",[316],{"path":2436,"title":2437,"description":2438,"meta":2439},"\u002Fko\u002Fdeclarative-vs-procedural-programming","선언적 프로그래밍 vs 절차형 프로그래밍","선언적 프로그래밍과 절차형 프로그래밍은 서로 대비되는 대표적인 패러다임이다. 두 패러다임의 차이를 알아보고, 각 패러다임에 해당하는 언어와 Vue\u002FReact의 특징을 살펴보자.",{"date":2440,"field":2441,"tags":2442},"2025.05.24","concepts",[2443,2444,2445],"paradigm","vue","react",{"path":2447,"title":2448,"description":2449,"meta":2450},"\u002Fko\u002Fdesign-guide","Design-Guide","A comprehensive guide on design principles and best practices for developers.",{"date":2451,"field":2410,"tags":2452},"2025.01.19",[2453],"design-system",{"path":2455,"title":2456,"description":2457,"meta":2458},"\u002Fko\u002Fdikw-pyramid","DIKW 피라미드 정리: 데이터가 지혜가 되기까지","Data, Information, Knowledge, Wisdom 4단계로 구성된 DIKW 피라미드 개념을 로그\u002F분석 데이터 같은 실제 개발 사례에 대입해서 정리한다.",{"date":2459,"field":2441,"tags":2460},"2026.09.18",[2461],"data",{"path":2463,"title":2464,"description":2465,"meta":2466},"\u002Fko\u002Fdisplaying-commits-with-github-actions","깃허브 액션으로 커밋 로그를 블로그에 띄우기: Nuxt와 Vercel 활용법","깃허브 액션을 통해 커밋 데이터를 자동으로 수집하고, Nuxt와 Vercel로 UI에 노출하는 방법을 소개합니다.",{"date":2467,"image":2468,"field":2469,"tags":2470},"2025.02.28","github-commits.jpg","infra",[2471,2472,2473],"github-actions","vercel","nuxt",{"path":2475,"title":2476,"description":2477,"meta":2478},"\u002Fko\u002Fdocker-auto-build-run","로컬에서 커밋 시 도커 빌드 후 컨테이너 시작하기","로컬에서 커밋 시 도커 빌드 후 컨테이너 시작하기.",{"date":2479,"field":2469,"tags":2480},"2025.03.10",[103],{"path":2482,"title":2483,"description":2484,"meta":2485},"\u002Fko\u002Fhow-to-mount-storage","라즈베리파이4에 저장장치 마운트 시키기","라즈베리파이에 저장장치를 연결하고 파티션 생성부터 포맷, 마운트까지의 전체 과정을 정리한 기록입니다.",{"date":2486,"field":2469,"tags":2487},"2025.07.11",[2488,2489],"raspberry-pi","linux",{"path":2491,"title":2492,"description":2492,"meta":2493},"\u002Fko\u002Fhtml","HTML이란 무엇인가",{"date":2408,"image":2494,"field":2410,"tags":2495},"html.jpg",[2420],{"path":2497,"title":2498,"description":2499,"meta":2500},"\u002Fko\u002Fhtml2","미치광이 카드 컴포넌트","미치광이 카드 컴포넌트는 어떤 느낌일까",{"date":2501,"field":2410,"tags":2502},"2025.03.28",[2420,2412],{"path":2504,"title":2505,"description":2505,"meta":2506},"\u002Fko\u002Fhttps-guide","Let's Encrypt를 이용해서 HTTPS 적용하기",{"date":2507,"field":2469,"tags":2508},"2025.03.11",[2509],"https",{"path":2511,"title":2512,"description":2513,"meta":2514},"\u002Fko\u002Fjs-data-01","JavaScript 데이터01","JavaScript 데이터 다루는 문제를 풀어보자",{"date":2515,"field":2410,"tags":2516},"2025.03.08",[316],{"path":2518,"title":2519,"description":2520,"meta":2521},"\u002Fko\u002Fjs-data-02","JavaScript 데이터02","복잡한 데이터 구조를 다루는 실전 문제를 해결해보자.",{"date":2515,"field":2410,"tags":2522},[316],{"path":2524,"title":2525,"description":2526,"meta":2527},"\u002Fko\u002Fmarkdown-guid","마크다운 문법","마크다운 문법을 알아보자",{"date":2528,"image":2529,"field":2398,"tags":2530},"2025.03.02","markdown.webp",[2531],"markdown",{"path":2386,"title":5,"description":17,"meta":2533},{"date":2381,"field":2382,"tags":2534},[2384,2385],{"path":2536,"title":2537,"description":2538,"meta":2539},"\u002Fko\u002Fnode-simple-server","Node.js 간단한 서버 만들기","Node.js로 간단한 HTTP 서버를 만드는 방법",{"date":2540,"field":2382,"tags":2541},"2025.01.30",[2385],{"path":2543,"title":2544,"description":2545,"meta":2546},"\u002Fko\u002Fnuxt-colormode","Nuxt3 다크모드 적용하기","Nuxt3에 다크모드 적용하여 관리하기",{"date":2418,"field":2410,"tags":2547},[2473],{"path":2549,"title":2550,"description":2551,"meta":2552},"\u002Fko\u002Fnuxt-content-vercel-empty-query-sqlite","배포만 하면 블로그 글이 하나도 안 보이던 버그: Nuxt Content와 Vercel의 \u002Ftmp 문제","로컬에서는 멀쩡한데 Vercel에 올리면 queryCollection()이 빈 배열만 반환하던 버그를 추적한 과정. 원인은 @nuxt\u002Fcontent가 Vercel의 읽기 전용 파일시스템을 지원하기 전 버전에 고정돼 있었던 것.",{"date":2553,"field":2398,"tags":2554},"2026.10.07",[2402,2472],{"path":2556,"title":2557,"description":2558,"meta":2559},"\u002Fko\u002Fnuxt-runtimeconfig-appconfig","Nuxt 설정 runtimeConfig vs app.config 완전 정복","Nuxt3에서 전역 설정을 어떻게 관리하고, 어떤 기준으로 나누어야 할까? 다크모드 예시부터 실무 기준 설정 전략까지 총정리",{"date":2560,"field":2410,"tags":2561},"2025.06.16",[2473],{"path":2563,"title":2564,"description":2565,"meta":2566},"\u002Fko\u002Fnuxt-useasyncdata-cache-error","useAsyncData 캐싱 문제와 Key 동적 설정으로 해결하기","이전 포스트의 데이터가 계속 출력되는 캐싱 관련 문제를 해결하는 방법",{"date":2567,"field":2410,"tags":2568,"image":2569},"2025.01.24",[2473],"cache.jpg",{"path":2571,"title":2572,"description":2573,"meta":2574},"\u002Fko\u002Fnuxt-vercel-sql-error","Nuxt 3에서 Vercel 배포 시 발생한 md 파일 로딩 문제 해결기","Nuxt 3 웹 어플리케이션에서 Vercel 배포 환경에서 발생한 md 파일 로딩 문제를 분석하고 해결한 방법에 대해 공유합니다.",{"date":2567,"image":2575,"field":2410,"tags":2576},"ssg.jpg",[2473,2402,2472],{"path":2578,"title":2579,"description":2580,"meta":2581},"\u002Fko\u002Fnuxt3-pinia-guide","nuxt3 Pinia 적용하기","Nuxt3 프로젝트에서 Pinia를 적용해보자 @pinia\u002Fnuxt",{"date":2418,"field":2410,"tags":2582},[2473,2583],"pinia",{"path":2585,"title":2586,"description":2587,"meta":2588},"\u002Fko\u002Fnuxt3-unit-test","Nuxt 프로젝트에 유닛 테스트 코드 추가하기","Nuxt 3 프로젝트에서 Vitest를 활용한 테스트 환경 설정과 유틸리티 함수 테스트 방법",{"date":2589,"image":2590,"field":2410,"tags":2591},"2025.03.07","unit-test.jpg",[2473,2592],"vitest",{"path":2594,"title":2595,"description":2596,"meta":2597},"\u002Fko\u002Fpm2-guide","프로세스 매니저 PM2","배포환경에서 유용하게 사용할 수 있는 프로세스 매니저를 알아보자.",{"date":2479,"field":2469,"tags":2598},[2599,2385],"pm2",{"path":2601,"title":2602,"description":2603,"meta":2604},"\u002Fko\u002Freact-createportal","React createPortal 완벽 가이드","React의 `createPortal`을 활용한 UI 구현법, 활용 사례, 장단점 및 코드 예제를 소개합니다.",{"date":2605,"field":2410,"tags":2606},"2025.01.27",[2445],{"path":2608,"title":2609,"description":2610,"meta":2611},"\u002Fko\u002Frouting","Routing","Nuxt file-system routing creates a route for every file in the pages\u002F directory.",{"date":2612,"field":2410,"tags":2613},"2025.01.22",[2473],{"path":2615,"title":2616,"description":2617,"meta":2618},"\u002Fko\u002Ftanstack-query-guide","TanStack Query란 무엇인가?","TanStack Query가 웹 애플리케이션에서 데이터 가져오기, 캐싱, 상태 관리를 어떻게 개선하는지 알아본다.",{"date":2619,"field":2410,"tags":2620},"2025.04.15",[2445,2621],"tanstack-query",{"path":2623,"title":2624,"description":2625,"meta":2626},"\u002Fko\u002Fuse-context","리엑트 상태관리 useContext()","React에서 `useContext`를 사용하면 컴포넌트 트리에 깊이 중첩된 컴포넌트에서도 prop drilling 없이 상태를 공유할 수 있습니다. 이 글에서는 `useContext`의 개념, 사용 방법, 장단점, 그리고 실전 예제를 함께 알아보겠습니다.",{"date":2627,"field":2410,"tags":2628},"2025.02.03",[2445],{"path":2630,"title":2631,"description":2632,"meta":2633},"\u002Fko\u002Fvite-plugin-style-inject","리엑티 컴포넌트를 패키지 빌드 시 CSS 같이 적용시키기","dist에 css에 파일이 생성되었으나, 적용 안 되는 이유를 알아보고 에러를 처리하자",{"date":2634,"field":2410,"tags":2635},"2025.04.06",[2636,2445],"vite",{"path":2638,"title":2639,"description":2640,"meta":2641},"\u002Fko\u002Fvue-component-guide","Vue의 \u003Ccomponent :is \u002F> 완벽 정리: 동적 렌더링의 모든 것","Vue에서 \u003Ccomponent :is \u002F>를 활용한 동적 컴포넌트 렌더링 방법과 VNode, 컴포넌트 객체의 차이를 실전 예제로 이해하기",{"date":2408,"field":2410,"tags":2642},[2444],{"path":2644,"title":2645,"description":2646,"meta":2647},"\u002Fko\u002Fvue-query-ref-value-trap-nuxt3","Vue Query에서 데이터가 안 보이는 이유? Nuxt 3와 Ref 구조의 함정","Nuxt 3 + vue-query를 사용할 때 API 데이터가 `v-if` 조건문에서 제대로 표시되지 않는 현상을 해결한 경험을 바탕으로, Ref 구조와 template 디렉티브에서의 차이를 자세히 설명합니다.",{"date":2648,"field":2410,"tags":2649},"2025.06.08",[2473,2444,2621],1791391656117]