{"id":11257,"date":"2025-07-08T17:46:42","date_gmt":"2025-07-08T08:46:42","guid":{"rendered":"https:\/\/www.stechstar.com\/user\/wordpress\/?p=11257"},"modified":"2026-08-20T03:33:13","modified_gmt":"2026-08-19T18:33:13","slug":"python-%ec%9d%b8%ea%b3%b5%ec%a7%80%eb%8a%a5-deploy-ml-models-with-fastapi-docker-and-heroku-tutorial","status":"publish","type":"post","link":"https:\/\/www.stechstar.com\/user\/wordpress\/python-%ec%9d%b8%ea%b3%b5%ec%a7%80%eb%8a%a5-deploy-ml-models-with-fastapi-docker-and-heroku-tutorial\/","title":{"rendered":"[python \uc778\uacf5\uc9c0\ub2a5] Deploy ML models with FastAPI, Docker, and Heroku | Tutorial"},"content":{"rendered":"<h1>[python \uc778\uacf5\uc9c0\ub2a5] Deploy ML models with FastAPI, Docker, and Heroku | Tutorial<\/h1>\n<p><iframe loading=\"lazy\" title=\"Deploy ML models with FastAPI, Docker, and Heroku | Tutorial\" src=\"https:\/\/www.youtube.com\/embed\/h5wLuVDr0oc\" width=\"480\" height=\"270\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\" data-mce-fragment=\"1\"><\/iframe><\/p>\n<article class=\"text-token-text-primary w-full\" dir=\"auto\" data-scroll-anchor=\"false\" data-testid=\"conversation-turn-2\">\n<div class=\"text-base my-auto mx-auto py-5 [--thread-content-margin:--spacing(4)] @[37rem]:[--thread-content-margin:--spacing(6)] @[72rem]:[--thread-content-margin:--spacing(16)] px-(--thread-content-margin)\">\n<div class=\"[--thread-content-max-width:32rem] @[34rem]:[--thread-content-max-width:40rem] @[64rem]:[--thread-content-max-width:48rem] mx-auto flex max-w-(--thread-content-max-width) flex-1 text-base gap-4 md:gap-5 lg:gap-6 group\/turn-messages focus-visible:outline-hidden\" tabindex=\"-1\">\n<div class=\"group\/conversation-turn relative flex w-full min-w-0 flex-col agent-turn\">\n<div class=\"relative flex-col gap-1 md:gap-3\">\n<div class=\"flex max-w-full flex-col grow\">\n<div class=\"min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-5\" dir=\"auto\" data-message-author-role=\"assistant\" data-message-id=\"9f99d2d8-0c02-42bf-872f-06c57ce4bca0\" data-message-model-slug=\"gpt-4o\">\n<div class=\"flex w-full flex-col gap-1 empty:hidden first:pt-[3px]\">\n<div class=\"markdown prose dark:prose-invert w-full break-words light\">&nbsp;<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/article>\n<article class=\"text-token-text-primary w-full\" dir=\"auto\" data-scroll-anchor=\"true\" data-testid=\"conversation-turn-4\">\n<div class=\"text-base my-auto mx-auto py-5 [--thread-content-margin:--spacing(4)] @[37rem]:[--thread-content-margin:--spacing(6)] @[72rem]:[--thread-content-margin:--spacing(16)] px-(--thread-content-margin)\">\n<div class=\"[--thread-content-max-width:32rem] @[34rem]:[--thread-content-max-width:40rem] @[64rem]:[--thread-content-max-width:48rem] mx-auto flex max-w-(--thread-content-max-width) flex-1 text-base gap-4 md:gap-5 lg:gap-6 group\/turn-messages focus-visible:outline-hidden\" tabindex=\"-1\">\n<div class=\"group\/conversation-turn relative flex w-full min-w-0 flex-col agent-turn\">\n<div class=\"relative flex-col gap-1 md:gap-3\">\n<article class=\"text-token-text-primary w-full\" dir=\"auto\" data-testid=\"conversation-turn-2\" data-scroll-anchor=\"false\">\n<div class=\"text-base my-auto mx-auto py-5 [--thread-content-margin:--spacing(4)] @[37rem]:[--thread-content-margin:--spacing(6)] @[72rem]:[--thread-content-margin:--spacing(16)] px-(--thread-content-margin)\">\n<div class=\"[--thread-content-max-width:32rem] @[34rem]:[--thread-content-max-width:40rem] @[64rem]:[--thread-content-max-width:48rem] mx-auto flex max-w-(--thread-content-max-width) flex-1 text-base gap-4 md:gap-5 lg:gap-6 group\/turn-messages focus-visible:outline-hidden\" tabindex=\"-1\">\n<div class=\"group\/conversation-turn relative flex w-full min-w-0 flex-col agent-turn\">\n<div class=\"relative flex-col gap-1 md:gap-3\">\n<div class=\"flex max-w-full flex-col grow\">\n<div class=\"min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-5\" dir=\"auto\" data-message-author-role=\"assistant\" data-message-id=\"9f99d2d8-0c02-42bf-872f-06c57ce4bca0\" data-message-model-slug=\"gpt-4o\">\n<div class=\"flex w-full flex-col gap-1 empty:hidden first:pt-[3px]\">\n<div class=\"markdown prose dark:prose-invert w-full break-words light\">\n<h3 data-start=\"108\" data-end=\"132\">&#x1f527; <strong data-start=\"115\" data-end=\"132\">\uba38\uc2e0\ub7ec\ub2dd \ubaa8\ub378 \ubc30\ud3ec \uc804\ubc18<\/strong><\/h3>\n<ul data-start=\"133\" data-end=\"281\">\n<li data-start=\"133\" data-end=\"212\">\n<p data-start=\"135\" data-end=\"212\"><strong data-start=\"135\" data-end=\"146\">(00:00)<\/strong> FastAPI + Docker + Heroku \uc870\ud569\uc73c\ub85c \uba38\uc2e0\ub7ec\ub2dd \ubaa8\ub378\uc744 \ud504\ub85c\ub355\uc158 \uc218\uc900\uc73c\ub85c \ubc30\ud3ec\ud558\ub294 \uc804\uccb4 \uacfc\uc815\uc744 \uc124\uba85.<\/p>\n<\/li>\n<li data-start=\"213\" data-end=\"281\">\n<p data-start=\"215\" data-end=\"281\">\ub2e4\uc591\ud55c \ud504\ub808\uc784\uc6cc\ud06c(Scikit-learn, TensorFlow, PyTorch)\uc5d0\uc11c\ub3c4 \ub3d9\uc77c\ud55c \uc811\uadfc \ubc29\uc2dd\uc73c\ub85c \ud65c\uc6a9 \uac00\ub2a5.<\/p>\n<\/li>\n<\/ul>\n<hr data-start=\"283\" data-end=\"286\">\n<h3 data-start=\"288\" data-end=\"309\">&#x1f9e0; <strong data-start=\"295\" data-end=\"309\">\ubaa8\ub378 \uac1c\ubc1c \ubc0f \uc800\uc7a5<\/strong><\/h3>\n<ul data-start=\"310\" data-end=\"546\">\n<li data-start=\"310\" data-end=\"546\">\n<p data-start=\"312\" data-end=\"331\"><strong data-start=\"312\" data-end=\"329\">(01:03~03:53)<\/strong><\/p>\n<ul data-start=\"334\" data-end=\"546\">\n<li data-start=\"334\" data-end=\"411\">\n<p data-start=\"336\" data-end=\"411\">Google Colab\uc5d0\uc11c \uc5b8\uc5b4 \uac10\uc9c0 \ubaa8\ub378 \uac1c\ubc1c (Scikit-learn \uae30\ubc18 Naive Bayes + CountVectorizer).<\/p>\n<\/li>\n<li data-start=\"414\" data-end=\"452\">\n<p data-start=\"416\" data-end=\"452\">\ubaa8\ub378\uc740 <code data-start=\"420\" data-end=\"430\">Pipeline<\/code>\uc73c\ub85c \uacb0\ud569\ud558\uc5ec \ucd94\ub860\uc744 \ub2e8\uc77c \uac1d\uccb4\ub85c \uc218\ud589.<\/p>\n<\/li>\n<li data-start=\"455\" data-end=\"504\">\n<p data-start=\"457\" data-end=\"504\"><code data-start=\"457\" data-end=\"465\">pickle<\/code>\ub85c \ubaa8\ub378 \uc800\uc7a5, \ubc84\uc804 \uad00\ub9ac(<code data-start=\"480\" data-end=\"499\">major.minor.patch<\/code>) \uad8c\uc7a5.<\/p>\n<\/li>\n<li data-start=\"507\" data-end=\"546\">\n<p data-start=\"509\" data-end=\"546\">\ud150\uc11c\ud50c\ub85c\ub098 \ud30c\uc774\ud1a0\uce58 \uc0ac\uc6a9 \uc2dc zip \uc555\ucd95\ud558\uc5ec \ub2e4\uc6b4\ub85c\ub4dc\ud558\ub294 \ud301 \uc18c\uac1c.<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr data-start=\"548\" data-end=\"551\">\n<h3 data-start=\"553\" data-end=\"582\">&#x1f3d7;&#xfe0f; <strong data-start=\"561\" data-end=\"582\">FastAPI \uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uad6c\uc131<\/strong><\/h3>\n<ul data-start=\"583\" data-end=\"772\">\n<li data-start=\"583\" data-end=\"772\">\n<p data-start=\"585\" data-end=\"604\"><strong data-start=\"585\" data-end=\"602\">(05:10~08:11)<\/strong><\/p>\n<ul data-start=\"607\" data-end=\"772\">\n<li data-start=\"607\" data-end=\"672\">\n<p data-start=\"609\" data-end=\"672\"><code data-start=\"609\" data-end=\"622\">app\/main.py<\/code>\uc5d0\uc11c FastAPI \uc5d4\ub4dc\ud3ec\uc778\ud2b8 \uad6c\ud604 (\ud5ec\uc2a4 \uccb4\ud06c \ubc0f <code data-start=\"651\" data-end=\"661\">\/predict<\/code> POST API).<\/p>\n<\/li>\n<li data-start=\"675\" data-end=\"715\">\n<p data-start=\"677\" data-end=\"715\"><code data-start=\"677\" data-end=\"687\">model.py<\/code>\uc5d0\uc11c \uc800\uc7a5\ub41c \ubaa8\ub378 \ub85c\ub4dc \ubc0f \uc804\ucc98\ub9ac\/\uc608\uce21 \ub85c\uc9c1 \uad6c\ud604.<\/p>\n<\/li>\n<li data-start=\"718\" data-end=\"772\">\n<p data-start=\"720\" data-end=\"772\"><code data-start=\"720\" data-end=\"740\">pydantic.BaseModel<\/code> \uae30\ubc18 \uc785\ub825\/\ucd9c\ub825 \uc720\ud6a8\uc131 \uac80\uc0ac \uc801\uc6a9 (\ud0c0\uc785 \uc548\uc804\uc131 \ubcf4\uc7a5).<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr data-start=\"774\" data-end=\"777\">\n<h3 data-start=\"779\" data-end=\"802\">&#x1f433; <strong data-start=\"786\" data-end=\"802\">Docker \ucee8\ud14c\uc774\ub108\ud654<\/strong><\/h3>\n<ul data-start=\"803\" data-end=\"1053\">\n<li data-start=\"803\" data-end=\"1053\">\n<p data-start=\"805\" data-end=\"824\"><strong data-start=\"805\" data-end=\"822\">(08:46~11:27)<\/strong><\/p>\n<ul data-start=\"827\" data-end=\"1053\">\n<li data-start=\"827\" data-end=\"900\">\n<p data-start=\"829\" data-end=\"900\">FastAPI \ucee8\ud14c\uc774\ub108\ud654\ub97c \uc704\ud55c <code data-start=\"847\" data-end=\"859\">Dockerfile<\/code>, <code data-start=\"861\" data-end=\"876\">.dockerignore<\/code>, <code data-start=\"878\" data-end=\"896\">requirements.txt<\/code> \uc0dd\uc131.<\/p>\n<\/li>\n<li data-start=\"903\" data-end=\"944\">\n<p data-start=\"905\" data-end=\"944\">\uacf5\uc2dd FastAPI + Gunicorn \uae30\ubc18 Docker \uc774\ubbf8\uc9c0 \ud65c\uc6a9.<\/p>\n<\/li>\n<li data-start=\"947\" data-end=\"997\">\n<p data-start=\"949\" data-end=\"997\">\uc758\uc874\uc131\uc740 scikit-learn\ub9cc \ucd94\uac00 \uc124\uce58 (<code data-start=\"975\" data-end=\"984\">==1.0.2<\/code> \ub4f1 \ubc84\uc804 \uace0\uc815 \uad8c\uc7a5).<\/p>\n<\/li>\n<li data-start=\"1000\" data-end=\"1053\">\n<p data-start=\"1002\" data-end=\"1053\"><code data-start=\"1002\" data-end=\"1016\">docker build<\/code>, <code data-start=\"1018\" data-end=\"1030\">docker run<\/code> \uba85\ub839\uc5b4\ub85c \ub85c\uceec \uc2e4\ud589 \ubc0f \ud3ec\ud2b8 \ub9e4\ud551 \ud655\uc778.<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr data-start=\"1055\" data-end=\"1058\">\n<h3 data-start=\"1060\" data-end=\"1085\">&#x1f4cb; <strong data-start=\"1067\" data-end=\"1085\">FastAPI \uc790\ub3d9 \ubb38\uc11c\ud654<\/strong><\/h3>\n<ul data-start=\"1086\" data-end=\"1195\">\n<li data-start=\"1086\" data-end=\"1195\">\n<p data-start=\"1088\" data-end=\"1107\"><strong data-start=\"1088\" data-end=\"1105\">(12:50~13:34)<\/strong><\/p>\n<ul data-start=\"1110\" data-end=\"1195\">\n<li data-start=\"1110\" data-end=\"1149\">\n<p data-start=\"1112\" data-end=\"1149\"><code data-start=\"1112\" data-end=\"1119\">\/docs<\/code> \uacbd\ub85c\uc5d0\uc11c Swagger \uae30\ubc18 API \ubb38\uc11c \uc790\ub3d9 \uc0dd\uc131.<\/p>\n<\/li>\n<li data-start=\"1152\" data-end=\"1195\">\n<p data-start=\"1154\" data-end=\"1195\">\uc785\ub825 \ud14d\uc2a4\ud2b8\uc5d0 \ub530\ub978 \uc5b8\uc5b4 \uc608\uce21 \ud14c\uc2a4\ud2b8 \uac00\ub2a5 (Postman \uc5c6\uc774\ub3c4 \uac00\ub2a5).<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr data-start=\"1197\" data-end=\"1200\">\n<h3 data-start=\"1202\" data-end=\"1222\">&#x2601;&#xfe0f; <strong data-start=\"1209\" data-end=\"1222\">Heroku \ubc30\ud3ec<\/strong><\/h3>\n<ul data-start=\"1223\" data-end=\"1458\">\n<li data-start=\"1223\" data-end=\"1458\">\n<p data-start=\"1225\" data-end=\"1244\"><strong data-start=\"1225\" data-end=\"1242\">(14:17~17:51)<\/strong><\/p>\n<ul data-start=\"1247\" data-end=\"1458\">\n<li data-start=\"1247\" data-end=\"1320\">\n<p data-start=\"1249\" data-end=\"1320\"><code data-start=\"1249\" data-end=\"1263\">heroku login<\/code>, <code data-start=\"1265\" data-end=\"1280\">heroku create<\/code>, <code data-start=\"1282\" data-end=\"1304\">git push heroku main<\/code>\uc73c\ub85c CLI \uae30\ubc18 \ubc30\ud3ec \uc9c4\ud589.<\/p>\n<\/li>\n<li data-start=\"1323\" data-end=\"1374\">\n<p data-start=\"1325\" data-end=\"1374\"><code data-start=\"1325\" data-end=\"1338\">heroku.yaml<\/code> \ud30c\uc77c \uc791\uc131 \ud544\uc218 (Heroku Dev Center \ubb38\uc11c \ucc38\uace0).<\/p>\n<\/li>\n<li data-start=\"1377\" data-end=\"1425\">\n<p data-start=\"1379\" data-end=\"1425\"><code data-start=\"1379\" data-end=\"1407\">heroku stack:set container<\/code>\ub85c Docker \uae30\ubc18 \uc2a4\ud0dd \uc124\uc815.<\/p>\n<\/li>\n<li data-start=\"1428\" data-end=\"1458\">\n<p data-start=\"1430\" data-end=\"1458\">\ubc30\ud3ec \ud6c4 Heroku URL\uc5d0\uc11c API \uc811\uadfc \uac00\ub2a5.<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr data-start=\"1460\" data-end=\"1463\">\n<h3 data-start=\"1465\" data-end=\"1483\">&#x2705; <strong data-start=\"1471\" data-end=\"1483\">\ubc30\ud3ec \ud6c4 \ud14c\uc2a4\ud2b8<\/strong><\/h3>\n<ul data-start=\"1484\" data-end=\"1643\">\n<li data-start=\"1484\" data-end=\"1643\">\n<p data-start=\"1486\" data-end=\"1505\"><strong data-start=\"1486\" data-end=\"1503\">(17:51~18:38)<\/strong><\/p>\n<ul data-start=\"1508\" data-end=\"1643\">\n<li data-start=\"1508\" data-end=\"1618\">\n<p data-start=\"1510\" data-end=\"1618\">Postman\uc73c\ub85c GET <code data-start=\"1524\" data-end=\"1527\">\/<\/code> \u2192 <code data-start=\"1530\" data-end=\"1547\">Health Check OK<\/code>, POST <code data-start=\"1554\" data-end=\"1564\">\/predict<\/code> \u2192 <code data-start=\"1567\" data-end=\"1589\">\"language\": \"german\"<\/code>, <code data-start=\"1591\" data-end=\"1614\">\"language\": \"italian\"<\/code> \ud655\uc778.<\/p>\n<\/li>\n<li data-start=\"1621\" data-end=\"1643\">\n<p data-start=\"1623\" data-end=\"1643\">\uc2e4\uc2dc\uac04 \uc5b8\uc5b4 \uac10\uc9c0 API \ubc30\ud3ec \uc644\ub8cc!<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr data-start=\"1645\" data-end=\"1648\">\n<p data-start=\"1650\" data-end=\"1690\" data-is-last-node=\"\" data-is-only-node=\"\">\ud544\uc694 \uc2dc \uac01 \uc139\uc158\ubcc4\ub85c \uad00\ub828 \ucf54\ub4dc\/\uad6c\uc131\uc694\uc18c\ub97c \ub2e4\uc2dc \uc815\ub9ac\ud574\ub4dc\ub9b4 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"flex min-h-[46px] justify-start\">\n<div class=\"touch:-me-2 touch:-ms-3.5 -ms-2.5 -me-1 flex flex-wrap items-center gap-y-4 p-1 select-none touch:w-[calc(100%+--spacing(3.5))] -mt-1 w-[calc(100%+--spacing(2.5))] duration-[1.5s] focus-within:transition-none hover:transition-none pointer-events-none [mask-image:linear-gradient(to_right,black_33%,transparent_66%)] [mask-size:300%_100%] [mask-position:100%_0%] motion-safe:transition-[mask-position] group-hover\/turn-messages:pointer-events-auto group-hover\/turn-messages:[mask-position:0_0] group-focus-within\/turn-messages:pointer-events-auto group-focus-within\/turn-messages:[mask-position:0_0] has-data-[state=open]:pointer-events-auto has-data-[state=open]:[mask-position:0_0]\"><button class=\"text-token-text-secondary hover:bg-token-bg-secondary rounded-lg\" aria-label=\"\ubcf5\uc0ac\" aria-selected=\"false\" data-testid=\"copy-turn-action-button\" data-state=\"closed\"><\/button><button class=\"text-token-text-secondary hover:bg-token-bg-secondary rounded-lg\" aria-label=\"\uc88b\uc740 \uc751\ub2f5\" aria-selected=\"false\" data-testid=\"good-response-turn-action-button\" data-state=\"closed\"><\/button><button class=\"text-token-text-secondary hover:bg-token-bg-secondary rounded-lg\" aria-label=\"\ubcc4\ub85c\uc778 \uc751\ub2f5\" aria-selected=\"false\" data-testid=\"bad-response-turn-action-button\" data-state=\"closed\"><\/button><button class=\"text-token-text-secondary hover:bg-token-bg-secondary rounded-lg\" aria-label=\"\uc18c\ub9ac \ub0b4\uc5b4 \uc77d\uae30\" aria-selected=\"false\" data-testid=\"voice-play-turn-action-button\" data-state=\"closed\"><\/button><button class=\"text-token-text-secondary hover:bg-token-bg-secondary rounded-lg\" aria-label=\"\uce94\ubc84\uc2a4\uc5d0\uc11c \ud3b8\uc9d1\" aria-selected=\"false\" data-state=\"closed\"><\/button><\/p>\n<div class=\"flex items-center\">&nbsp;<\/div>\n<p><button class=\"text-token-text-secondary hover:bg-token-bg-secondary rounded-lg\" aria-label=\"\uacf5\uc720\ud558\uae30\" aria-selected=\"false\" data-state=\"closed\"><\/button><\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/article>\n<article class=\"text-token-text-primary w-full\" dir=\"auto\" data-testid=\"conversation-turn-3\" data-scroll-anchor=\"false\">\n<h5 class=\"sr-only\">\ub098\uc758 \ub9d0:<\/h5>\n<div class=\"text-base my-auto mx-auto py-5 [--thread-content-margin:--spacing(4)] @[37rem]:[--thread-content-margin:--spacing(6)] @[72rem]:[--thread-content-margin:--spacing(16)] px-(--thread-content-margin)\">\n<div class=\"[--thread-content-max-width:32rem] @[34rem]:[--thread-content-max-width:40rem] @[64rem]:[--thread-content-max-width:48rem] mx-auto flex max-w-(--thread-content-max-width) flex-1 text-base gap-4 md:gap-5 lg:gap-6 group\/turn-messages focus-visible:outline-hidden\" tabindex=\"-1\">\n<div class=\"group\/conversation-turn relative flex w-full min-w-0 flex-col\">\n<div class=\"relative flex-col gap-1 md:gap-3\">\n<div class=\"flex max-w-full flex-col grow\">\n<div class=\"min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-5\" dir=\"auto\" data-message-author-role=\"user\" data-message-id=\"ec4e7d21-d64f-4d72-b3a3-829e6a3a3702\">\n<div class=\"flex w-full flex-col gap-1 empty:hidden items-end rtl:items-start\">\n<div class=\"relative max-w-[var(--user-chat-width,70%)] bg-token-message-surface rounded-3xl px-5 py-2.5\">\n<div class=\"whitespace-pre-wrap\">\uc139\uc158\ubcc4\ub85c \uad00\ub828 \ucf54\ub4dc\/\uad6c\uc131\uc694\uc18c\ub97c \ub2e4\uc2dc \uc815\ub9ac<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"flex justify-end\">\n<div class=\"touch:-me-2 touch:-ms-3.5 -ms-2.5 -me-1 flex flex-wrap items-center gap-y-4 p-1 select-none focus-within:transition-none hover:transition-none duration-300 group-hover\/turn-messages:delay-300 pointer-events-none opacity-0 motion-safe:transition-opacity group-hover\/turn-messages:pointer-events-auto group-hover\/turn-messages:opacity-100 group-focus-within\/turn-messages:pointer-events-auto group-focus-within\/turn-messages:opacity-100 has-data-[state=open]:pointer-events-auto has-data-[state=open]:opacity-100\">&nbsp;<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/article>\n<article class=\"text-token-text-primary w-full\" dir=\"auto\" data-testid=\"conversation-turn-4\" data-scroll-anchor=\"true\">\n<h6 class=\"sr-only\">ChatGPT\uc758 \ub9d0:<\/h6>\n<div class=\"text-base my-auto mx-auto py-5 [--thread-content-margin:--spacing(4)] @[37rem]:[--thread-content-margin:--spacing(6)] @[72rem]:[--thread-content-margin:--spacing(16)] px-(--thread-content-margin)\">\n<div class=\"[--thread-content-max-width:32rem] @[34rem]:[--thread-content-max-width:40rem] @[64rem]:[--thread-content-max-width:48rem] mx-auto flex max-w-(--thread-content-max-width) flex-1 text-base gap-4 md:gap-5 lg:gap-6 group\/turn-messages focus-visible:outline-hidden\" tabindex=\"-1\">\n<div class=\"group\/conversation-turn relative flex w-full min-w-0 flex-col agent-turn\">\n<div class=\"relative flex-col gap-1 md:gap-3\">\n<div class=\"flex max-w-full flex-col grow\">\n<div class=\"min-h-8 text-message relative flex w-full flex-col items-end gap-2 text-start break-words whitespace-normal [.text-message+&amp;]:mt-5\" dir=\"auto\" data-message-author-role=\"assistant\" data-message-id=\"25683752-0d8c-47ba-bebb-49c72a991051\" data-message-model-slug=\"gpt-4o\">\n<div class=\"flex w-full flex-col gap-1 empty:hidden first:pt-[3px]\">\n<div class=\"markdown prose dark:prose-invert w-full break-words light\">\n<p data-start=\"0\" data-end=\"98\">\uc544\ub798\ub294 \uc601\uc0c1\uc5d0\uc11c \uc124\uba85\ud55c <strong data-start=\"13\" data-end=\"58\">FastAPI + Docker + Heroku\ub97c \ud65c\uc6a9\ud55c \uba38\uc2e0\ub7ec\ub2dd \ubaa8\ub378 \ubc30\ud3ec<\/strong> \uacfc\uc815\uc744 <strong data-start=\"63\" data-end=\"90\">\uc139\uc158\ubcc4\ub85c \ucf54\ub4dc \ubc0f \uad6c\uc131\uc694\uc18c \uc911\uc2ec\uc73c\ub85c \uc7ac\uc815\ub9ac<\/strong>\ud55c \ub0b4\uc6a9\uc785\ub2c8\ub2e4:<\/p>\n<hr data-start=\"100\" data-end=\"103\">\n<h2 data-start=\"105\" data-end=\"130\">1&#xfe0f;&#x20e3; \ubaa8\ub378 \uac1c\ubc1c \ubc0f \uc800\uc7a5 (Colab)<\/h2>\n<h3 data-start=\"132\" data-end=\"144\">&#x2705; \uc0ac\uc6a9 \uae30\uc220:<\/h3>\n<ul data-start=\"145\" data-end=\"187\">\n<li data-start=\"145\" data-end=\"187\">\n<p data-start=\"147\" data-end=\"187\"><code data-start=\"147\" data-end=\"161\">scikit-learn<\/code>, <code data-start=\"163\" data-end=\"171\">pandas<\/code>, <code data-start=\"173\" data-end=\"177\">re<\/code>, <code data-start=\"179\" data-end=\"187\">pickle<\/code><\/p>\n<\/li>\n<\/ul>\n<h3 data-start=\"189\" data-end=\"202\">&#x1f4c4; \uc8fc\uc694 \ucf54\ub4dc:<\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">python<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-python\"><span class=\"hljs-keyword\">from<\/span> sklearn.pipeline <span class=\"hljs-keyword\">import<\/span> Pipeline<br \/>\n<span class=\"hljs-keyword\">from<\/span> sklearn.feature_extraction.text <span class=\"hljs-keyword\">import<\/span> CountVectorizer<br \/>\n<span class=\"hljs-keyword\">from<\/span> sklearn.naive_bayes <span class=\"hljs-keyword\">import<\/span> MultinomialNB<br \/>\n<span class=\"hljs-keyword\">from<\/span> sklearn.preprocessing <span class=\"hljs-keyword\">import<\/span> LabelEncoder<br \/>\n<span class=\"hljs-keyword\">from<\/span> sklearn.model_selection <span class=\"hljs-keyword\">import<\/span> train_test_split<br \/>\n<span class=\"hljs-keyword\">import<\/span> pickle<\/p>\n<p><span class=\"hljs-comment\"># \ub370\uc774\ud130 \uc900\ube44 \ubc0f \uc804\ucc98\ub9ac<\/span><br \/>\nlabel_encoder = LabelEncoder()<br \/>\ny_encoded = label_encoder.fit_transform(df[<span class=\"hljs-string\">'language'<\/span>])<\/p>\n<p><span class=\"hljs-comment\"># \ubaa8\ub378 \ud30c\uc774\ud504\ub77c\uc778 \uc815\uc758<\/span><br \/>\npipeline = Pipeline([<br \/>\n    (<span class=\"hljs-string\">'vectorizer'<\/span>, CountVectorizer()),<br \/>\n    (<span class=\"hljs-string\">'classifier'<\/span>, MultinomialNB())<br \/>\n])<\/p>\n<p><span class=\"hljs-comment\"># \ud6c8\ub828<\/span><br \/>\npipeline.fit(df[<span class=\"hljs-string\">'text'<\/span>], y_encoded)<\/p>\n<p><span class=\"hljs-comment\"># \uc800\uc7a5<\/span><br \/>\n<span class=\"hljs-keyword\">with<\/span> <span class=\"hljs-built_in\">open<\/span>(<span class=\"hljs-string\">'model_v0.1.0.pkl'<\/span>, <span class=\"hljs-string\">'wb'<\/span>) <span class=\"hljs-keyword\">as<\/span> f:<br \/>\n    pickle.dump(pipeline, f)<\/p>\n<p><span class=\"hljs-comment\"># \ub808\uc774\ube14 \uc800\uc7a5 (\uc120\ud0dd)<\/span><br \/>\nlabel_classes = label_encoder.classes_<br \/>\n<\/code><\/div>\n<\/div>\n<hr data-start=\"867\" data-end=\"870\">\n<h2 data-start=\"872\" data-end=\"891\">2&#xfe0f;&#x20e3; FastAPI \uc571 \uad6c\uc131<\/h2>\n<h3 data-start=\"893\" data-end=\"908\">&#x1f4c1; \ub514\ub809\ud130\ub9ac \uad6c\uc870:<\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">bash<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre!\">app\/<br \/>\n\u251c\u2500\u2500 main.py              <span class=\"hljs-comment\"># FastAPI \uc5d4\ud2b8\ub9ac\ud3ec\uc778\ud2b8<\/span><br \/>\n\u251c\u2500\u2500 model\/<br \/>\n\u2502   \u251c\u2500\u2500 model.py         <span class=\"hljs-comment\"># \ubaa8\ub378 \ub85c\ub529 \ubc0f \uc608\uce21 \ud568\uc218<\/span><br \/>\n\u2502   \u2514\u2500\u2500 model_v0.1.0.pkl <span class=\"hljs-comment\"># \uc800\uc7a5\ub41c \ubaa8\ub378 \ud30c\uc77c<\/span><br \/>\n<\/code><\/div>\n<\/div>\n<h3 data-start=\"1054\" data-end=\"1090\">&#x1f4c4; <code data-start=\"1061\" data-end=\"1077\">model\/model.py<\/code> (\ubaa8\ub378 \ub85c\ub529 \ubc0f \uc608\uce21)<\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">python<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-python\"><span class=\"hljs-keyword\">import<\/span> pickle<br \/>\n<span class=\"hljs-keyword\">import<\/span> os<br \/>\n<span class=\"hljs-keyword\">import<\/span> re<\/p>\n<p>BASE_DIR = os.path.dirname(__file__)<br \/>\nMODEL_PATH = os.path.join(BASE_DIR, <span class=\"hljs-string\">\"model_v0.1.0.pkl\"<\/span>)<\/p>\n<p><span class=\"hljs-keyword\">with<\/span> <span class=\"hljs-built_in\">open<\/span>(MODEL_PATH, <span class=\"hljs-string\">\"rb\"<\/span>) <span class=\"hljs-keyword\">as<\/span> f:<br \/>\n    model = pickle.load(f)<\/p>\n<p>label_classes = [<span class=\"hljs-string\">\"english\"<\/span>, <span class=\"hljs-string\">\"german\"<\/span>, <span class=\"hljs-string\">\"italian\"<\/span>, ...]  <span class=\"hljs-comment\"># \uc2e4\uc81c label list<\/span><\/p>\n<p><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title function_\">predict_pipeline<\/span>(<span class=\"hljs-params\">text: <span class=\"hljs-built_in\">str<\/span><\/span>) -&gt; <span class=\"hljs-built_in\">str<\/span>:<br \/>\n    text = re.sub(<span class=\"hljs-string\">r\"[^\\w\\s]\"<\/span>, <span class=\"hljs-string\">\"\"<\/span>, text)<br \/>\n    pred = model.predict([text])[<span class=\"hljs-number\">0<\/span>]<br \/>\n    <span class=\"hljs-keyword\">return<\/span> label_classes[pred]<br \/>\n<\/code><\/div>\n<\/div>\n<h3 data-start=\"1516\" data-end=\"1548\">&#x1f4c4; <code data-start=\"1523\" data-end=\"1532\">main.py<\/code> (FastAPI \uc5d4\ub4dc\ud3ec\uc778\ud2b8)<\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">python<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-python\"><span class=\"hljs-keyword\">from<\/span> fastapi <span class=\"hljs-keyword\">import<\/span> FastAPI<br \/>\n<span class=\"hljs-keyword\">from<\/span> pydantic <span class=\"hljs-keyword\">import<\/span> BaseModel<br \/>\n<span class=\"hljs-keyword\">from<\/span> app.model.model <span class=\"hljs-keyword\">import<\/span> predict_pipeline<\/p>\n<p>app = FastAPI()<\/p>\n<p><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title class_\">TextIn<\/span>(<span class=\"hljs-title class_ inherited__\">BaseModel<\/span>):<br \/>\n    text: <span class=\"hljs-built_in\">str<\/span><\/p>\n<p><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title class_\">PredictionOut<\/span>(<span class=\"hljs-title class_ inherited__\">BaseModel<\/span>):<br \/>\n    language: <span class=\"hljs-built_in\">str<\/span><\/p>\n<p><span class=\"hljs-meta\">@app.get(<span class=\"hljs-params\"><span class=\"hljs-string\">\"\/\"<\/span><\/span><\/span>)<br \/>\n<span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title function_\">health_check<\/span>():<br \/>\n    <span class=\"hljs-keyword\">return<\/span> {<span class=\"hljs-string\">\"status\"<\/span>: <span class=\"hljs-string\">\"ok\"<\/span>, <span class=\"hljs-string\">\"model_version\"<\/span>: <span class=\"hljs-string\">\"0.1.0\"<\/span>}<\/p>\n<p><span class=\"hljs-meta\">@app.post(<span class=\"hljs-params\"><span class=\"hljs-string\">\"\/predict\"<\/span><\/span><\/span>, response_model=PredictionOut)<br \/>\n<span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title function_\">predict<\/span>(<span class=\"hljs-params\">payload: TextIn<\/span>):<br \/>\n    language = predict_pipeline(payload.text)<br \/>\n    <span class=\"hljs-keyword\">return<\/span> {<span class=\"hljs-string\">\"language\"<\/span>: language}<br \/>\n<\/code><\/div>\n<\/div>\n<hr data-start=\"2028\" data-end=\"2031\">\n<h2 data-start=\"2033\" data-end=\"2049\">3&#xfe0f;&#x20e3; Docker \uad6c\uc131<\/h2>\n<h3 data-start=\"2051\" data-end=\"2070\">&#x1f4c4; <code data-start=\"2058\" data-end=\"2070\">Dockerfile<\/code><\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">dockerfile<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-dockerfile\">FROM tiangolo\/uvicorn-gunicorn-fastapi:python3.8<\/p>\n<p>WORKDIR \/app<br \/>\nCOPY .\/app \/app\/app<br \/>\nCOPY requirements.txt \/app<\/p>\n<p>RUN pip install --no-cache-dir -r \/app\/requirements.txt<br \/>\n<\/code><\/div>\n<\/div>\n<h3 data-start=\"2257\" data-end=\"2282\">&#x1f4c4; <code data-start=\"2264\" data-end=\"2282\">requirements.txt<\/code><\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">txt<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-txt\">scikit-learn==1.0.2<br \/>\n<\/code><\/div>\n<\/div>\n<h3 data-start=\"2315\" data-end=\"2337\">&#x1f4c4; <code data-start=\"2322\" data-end=\"2337\">.dockerignore<\/code><\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">txt<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-txt\">__pycache__\/<br \/>\n*.pyc<br \/>\n*.pkl<br \/>\n*.zip<br \/>\n.venv<br \/>\n<\/code><\/div>\n<\/div>\n<hr data-start=\"2387\" data-end=\"2390\">\n<h2 data-start=\"2392\" data-end=\"2416\">4&#xfe0f;&#x20e3; \ub85c\uceec Docker \ud14c\uc2a4\ud2b8 \uba85\ub839\uc5b4<\/h2>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">bash<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-bash\"><span class=\"hljs-comment\"># \ube4c\ub4dc<\/span><br \/>\ndocker build -t language-detection-app .<\/p>\n<p><span class=\"hljs-comment\"># \uc2e4\ud589 (\ud3ec\ud2b8 \ub9e4\ud551)<\/span><br \/>\ndocker run -p 8080:80 language-detection-app<br \/>\n<\/code><\/div>\n<\/div>\n<hr data-start=\"2536\" data-end=\"2539\">\n<h2 data-start=\"2541\" data-end=\"2562\">5&#xfe0f;&#x20e3; FastAPI \uc790\ub3d9 \ubb38\uc11c\ud654<\/h2>\n<h3 data-start=\"2564\" data-end=\"2571\">\uc811\uc18d:<\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">bash<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre!\">http:\/\/localhost:8080\/docs<br \/>\n<\/code><\/div>\n<\/div>\n<p data-start=\"2608\" data-end=\"2654\">\uc790\ub3d9 \uc0dd\uc131\ub41c Swagger UI\uc5d0\uc11c <code data-start=\"2628\" data-end=\"2638\">\/predict<\/code> POST API \ud14c\uc2a4\ud2b8 \uac00\ub2a5<\/p>\n<hr data-start=\"2656\" data-end=\"2659\">\n<h2 data-start=\"2661\" data-end=\"2680\">6&#xfe0f;&#x20e3; Heroku \ubc30\ud3ec \uad6c\uc131<\/h2>\n<h3 data-start=\"2682\" data-end=\"2701\">&#x1f4c4; <code data-start=\"2689\" data-end=\"2701\">heroku.yml<\/code><\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">yaml<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-yaml\"><span class=\"hljs-attr\">build:<\/span><br \/>\n  <span class=\"hljs-attr\">docker:<\/span><br \/>\n    <span class=\"hljs-attr\">web:<\/span> <span class=\"hljs-string\">Dockerfile<\/span><br \/>\n<span class=\"hljs-attr\">run:<\/span><br \/>\n  <span class=\"hljs-attr\">web:<\/span> <span class=\"hljs-string\">uvicorn<\/span> <span class=\"hljs-string\">app.main:app<\/span> <span class=\"hljs-string\">--host=0.0.0.0<\/span> <span class=\"hljs-string\">--port=${PORT:-5000}<\/span><br \/>\n<\/code><\/div>\n<\/div>\n<h3 data-start=\"2821\" data-end=\"2843\">&#x1f9ea; Heroku CLI \uba85\ub839\uc5b4:<\/h3>\n<div class=\"contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary\">\n<div class=\"flex items-center text-token-text-secondary px-4 py-2 text-xs font-sans justify-between h-9 bg-token-sidebar-surface-primary select-none rounded-t-2xl\">bash<\/div>\n<div class=\"sticky top-9\">\n<div class=\"absolute end-0 bottom-0 flex h-9 items-center pe-2\">\n<div class=\"bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs\"><button class=\"flex gap-1 items-center select-none py-1\" aria-label=\"\ubcf5\uc0ac\">\ubcf5\uc0ac<\/button><span class=\"\" data-state=\"closed\"><button class=\"flex items-center gap-1 py-1 select-none\">\ud3b8\uc9d1<\/button><\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"overflow-y-auto p-4\" dir=\"ltr\"><code class=\"whitespace-pre! language-bash\">heroku login<br \/>\nheroku create language-detection-app123<br \/>\nheroku stack:<span class=\"hljs-built_in\">set<\/span> container<br \/>\ngit add .<br \/>\ngit commit -m <span class=\"hljs-string\">\"Deploy to Heroku\"<\/span><br \/>\ngit push heroku main<br \/>\n<\/code><\/div>\n<\/div>\n<hr data-start=\"3001\" data-end=\"3004\">\n<h2 data-start=\"3006\" data-end=\"3018\">7&#xfe0f;&#x20e3; \ubc30\ud3ec \ud655\uc778<\/h2>\n<ul data-start=\"3020\" data-end=\"3129\">\n<li data-start=\"3020\" data-end=\"3086\">\n<p data-start=\"3022\" data-end=\"3086\"><strong data-start=\"3022\" data-end=\"3035\">\ubc30\ud3ec URL \uc608\uc2dc<\/strong>: <code data-start=\"3037\" data-end=\"3086\">https:\/\/language-detection-app123.herokuapp.com<\/code><\/p>\n<\/li>\n<li data-start=\"3087\" data-end=\"3103\">\n<p data-start=\"3089\" data-end=\"3103\">GET <code data-start=\"3093\" data-end=\"3096\">\/<\/code> \u2192 \ud5ec\uc2a4\uccb4\ud06c<\/p>\n<\/li>\n<li data-start=\"3104\" data-end=\"3129\">\n<p data-start=\"3106\" data-end=\"3129\">POST <code data-start=\"3111\" data-end=\"3121\">\/predict<\/code> \u2192 \uc5b8\uc5b4 \uc608\uce21<\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/article>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/article>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_11257\" class=\"pvc_stats all  \" data-element-id=\"11257\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/www.stechstar.com\/user\/wordpress\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"excerpt":{"rendered":"<p>[python \uc778\uacf5\uc9c0\ub2a5] Deploy ML models with FastAPI, Docker, and Heroku | Tutorial &nbsp; &#x1f527; \uba38\uc2e0\ub7ec\ub2dd \ubaa8\ub378 \ubc30\ud3ec \uc804\ubc18 (00:00) FastAPI + Docker + Heroku \uc870\ud569\uc73c\ub85c \uba38\uc2e0\ub7ec\ub2dd \ubaa8\ub378\uc744 \ud504\ub85c\ub355\uc158 \uc218\uc900\uc73c\ub85c \ubc30\ud3ec\ud558\ub294 \uc804\uccb4 \uacfc\uc815\uc744 \uc124\uba85. \ub2e4\uc591\ud55c \ud504\ub808\uc784\uc6cc\ud06c(Scikit-learn, TensorFlow, PyTorch)\uc5d0\uc11c\ub3c4 \ub3d9\uc77c\ud55c \uc811\uadfc \ubc29\uc2dd\uc73c\ub85c \ud65c\uc6a9 \uac00\ub2a5. &#x1f9e0; \ubaa8\ub378 \uac1c\ubc1c \ubc0f \uc800\uc7a5 (01:03~03:53) Google Colab\uc5d0\uc11c \uc5b8\uc5b4 \uac10\uc9c0 \ubaa8\ub378 \uac1c\ubc1c (Scikit-learn [&hellip;]<\/p>\n<div class=\"pvc_clear\"><\/div>\n<p id=\"pvc_stats_11257\" class=\"pvc_stats all  \" data-element-id=\"11257\" style=\"\"><i class=\"pvc-stats-icon medium\" aria-hidden=\"true\"><svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"far\" data-icon=\"chart-bar\" role=\"img\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\" class=\"svg-inline--fa fa-chart-bar fa-w-16 fa-2x\"><path fill=\"currentColor\" d=\"M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z\" class=\"\"><\/path><\/svg><\/i> <img loading=\"lazy\" decoding=\"async\" width=\"16\" height=\"16\" alt=\"Loading\" src=\"https:\/\/www.stechstar.com\/user\/wordpress\/wp-content\/plugins\/page-views-count\/ajax-loader-2x.gif\" border=0 \/><\/p>\n<div class=\"pvc_clear\"><\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[21,24,20],"tags":[],"class_list":["post-11257","post","type-post","status-publish","format-standard","hentry","category-21","category-24","category-20"],"a3_pvc":{"activated":true,"total_views":22,"today_views":0},"_links":{"self":[{"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/posts\/11257","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/comments?post=11257"}],"version-history":[{"count":3,"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/posts\/11257\/revisions"}],"predecessor-version":[{"id":11916,"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/posts\/11257\/revisions\/11916"}],"wp:attachment":[{"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/media?parent=11257"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/categories?post=11257"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.stechstar.com\/user\/wordpress\/wp-json\/wp\/v2\/tags?post=11257"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}