{
  "source_file": "0930",
  "source_platform": "Medium",
  "author": "Alexander Davis",
  "title_listed": "Journey to Full-Stack Data Scientist: Model Deployment",
  "keywords": "",
  "domain": "机器学习",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://towardsdatascience.com/journey-to-full-stack-data-scientist-model-deployment-f385f244ec67/",
  "publish_date": "2025-01-04",
  "corpus_stage": "",
  "notes": "",
  "id": "f7c049d9569d",
  "collected_at": "2026-10-02T01:57:54",
  "title": "Journey to Full-Stack Data Scientist: Model Deployment | Towards Data Science",
  "body_markdown": "![Revisit consent button](../images/body-df29beacd7496220cc94.svg)\nWe value your privacy\nWe use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.\nCustomise Reject All Accept All\nCustomise Consent Preferences ![](../images/body-2159bb67f3de8ad870eb.svg)\nWe use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.\nThe cookies that are categorised as \"Necessary\" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more\nNecessaryAlways Active\nNecessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.\n  * Cookie\nBCTempID\n  * Duration\n10 minutes\n  * Description\nNo description available.\n\n\n  * Cookie\n__cf_bm\n  * Duration\n1 hour\n  * Description\nThis cookie, set by Cloudflare, is used to support Cloudflare Bot Management. \n\n\n  * Cookie\nAWSALBCORS\n  * Duration\n7 days\n  * Description\nAmazon Web Services set this cookie for load balancing.\n\n\n  * Cookie\n_cfuvid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services\n\n\n  * Cookie\nli_gc\n  * Duration\n6 months\n  * Description\nLinkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.\n\n\n  * Cookie\n__hssrc\n  * Duration\nsession\n  * Description\nThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.\n\n\n  * Cookie\n__hssc\n  * Duration\n1 hour\n  * Description\nHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.\n\n\n  * Cookie\nwpEmojiSettingsSupports\n  * Duration\nsession\n  * Description\nWordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.\n\n\n  * Cookie\nBCSessionID\n  * Duration\n1 year 1 month 4 days\n  * Description\nBlueconic sets this cookie as a unique identifier for the BlueConic profile.\n\n\n  * Cookie\n_octo\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nlogged_in\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\n__Secure-YEC\n  * Duration\npast\n  * Description\nYouTube sets this cookie to stores the user's video player preferences using embedded YouTube video\n\n\n  * Cookie\n__eoi\n  * Duration\n6 months\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTGCORS\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\nlogin-status-p\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTG\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\ncsrf_token\n  * Duration\nsession\n  * Description\nNo description available.\n\n\n  * Cookie\ntoken_v2\n  * Duration\n1 day\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nD\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nPHPSESSID\n  * Duration\nsession\n  * Description\nThis cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.\n\n\n  * Cookie\nVISITOR_PRIVACY_METADATA\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to store the user's cookie consent state for the current domain. \n\n\n  * Cookie\ncookietest\n  * Duration\nsession\n  * Description\nThe cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.\n\n\n  * Cookie\n__Host-airtable-session\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\n__Host-airtable-session.sig\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\nm\n  * Duration\n1 year 1 month 4 days\n  * Description\nStripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.\n\n\n  * Cookie\nBIGipServer*\n  * Duration\nsession\n  * Description\nMarketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. \n\n\n  * Cookie\n__cfruid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to identify trusted web traffic.\n\n\n  * Cookie\n_GRECAPTCHA\n  * Duration\n6 months\n  * Description\nGoogle Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.\n\n\n  * Cookie\n__Secure-YNID\n  * Duration\n6 months\n  * Description\nGoogle cookie used to protect user security and prevent fraud, especially during the login process.\n\n\n  * Cookie\ncookieyes-consent\n  * Duration\n1 year\n  * Description\nCookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.\n\n\nFunctional\nFunctional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.\n  * Cookie\nlidc\n  * Duration\n1 day\n  * Description\nLinkedIn sets the lidc cookie to facilitate data center selection.\n\n\n  * Cookie\nbrw\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nbrwConsent\n  * Duration\n5 minutes\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nWMF-Uniq\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nloom_anon_comment\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nloom_referral_video\n  * Duration\nsession\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nVISITOR_INFO1_LIVE\n  * Duration\n6 months\n  * Description\nA cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.\n\n\n  * Cookie\nyt-remote-connected-devices\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nytidb::LAST_RESULT_ENTRY_KEY\n  * Duration\nNever Expires\n  * Description\nThe cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.\n\n\n  * Cookie\nyt-remote-device-id\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-name\n  * Duration\nsession\n  * Description\nThe yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\nyt-remote-fast-check-period\n  * Duration\nsession\n  * Description\nThe yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-app\n  * Duration\nsession\n  * Description\nThe yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-available\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-installed\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\ncp_session\n  * Duration\n3 months\n  * Description\nCodepen sets this cookie for Help systems found in the website.\n\n\n  * Cookie\nloid\n  * Duration\n1 year 1 month 4 days\n  * Description\nThis cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.\n\n\nAnalytics\nAnalytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.\n  * Cookie\n__hstc\n  * Duration\n6 months\n  * Description\nHubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).\n\n\n  * Cookie\nhubspotutk\n  * Duration\n6 months\n  * Description\nHubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.\n\n\n  * Cookie\n_ga\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.\n\n\n  * Cookie\n_ga_*\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to store and count page views.\n\n\n  * Cookie\n__Host-psifi.analyticsTrace\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n__Host-psifi.analyticsTraceV2\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n_gh_sess\n  * Duration\nsession\n  * Description\nGitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.\n\n\n  * Cookie\nYSC\n  * Duration\nsession\n  * Description\nYSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.\n\n\n  * Cookie\najs_anonymous_id\n  * Duration\n1 year\n  * Description\nThis cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.\n\n\n  * Cookie\nvuid\n  * Duration\n1 year 1 month 4 days\n  * Description\nVimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. \n\n\nPerformance\nPerformance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.\n  * Cookie\nAWSALB\n  * Duration\n7 days\n  * Description\nAWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.\n\n\n  * Cookie\nacq\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nacq.sig\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nptc\n  * Duration\n2 years\n  * Description\nNo description available.\n\n\nAdvertisement\nAdvertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.\n  * Cookie\nmuc_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to collect user behaviour and interaction data to optimize the website.\n\n\n  * Cookie\nguest_id_marketing\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\nguest_id_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\npersonalization_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.\n\n\n  * Cookie\nguest_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.\n\n\n  * Cookie\nbcookie\n  * Duration\n1 year\n  * Description\nLinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.\n\n\n  * Cookie\n__Secure-ROLLOUT_TOKEN\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.\n\n\n  * Cookie\nyt.innertube::nextId\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nyt.innertube::requests\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nsession_tracker\n  * Duration\nsession\n  * Description\nThis cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.\n\n\n  * Cookie\nedgebucket\n  * Duration\nsession\n  * Description\nReddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.\n\n\n  * Cookie\ndid\n  * Duration\n1 year\n  * Description\nArbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.\n\n\nUncategorised\nOther uncategorised cookies are those that are being analysed and have not been classified into a category as yet.\nNo cookies to display.\nReject All Save My Preferences Accept All\nToggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation\n[](https://towardsdatascience.com/)\nPublish AI, ML & data-science insights to a global community of data professionals.\nSign in[Submit an Article](https://write.insightmediagroup.io)\n[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)\n* * *\n[Write For TDS](https://towardsdatascience.com/submissions/)\n[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search\n[Data Science](https://towardsdatascience.com/category/data-science/)\n# Journey to Full-Stack Data Scientist: Model Deployment\nAn introduction to productionizing a machine learning model using APIs and Docker.\n[Alex Davis](https://towardsdatascience.com/author/adavis08/)\nJanuary 4, 20259 min read\n### An introduction to productionizing machine learning models using APIs and Docker.\n## Growing Responsibilities of Data Scientists\nThe title of data scientist is ever-changing and often vague. It usually involves one who is fluent in mathematics, programming, and machine learning. They spend time cleaning data, building models, fine-tuning, and conducting experimentation. They must also have great communication skills, a good grasp on their domain, and other soft skills.\nHowever, this is not always exactly the case. If you spend enough time scrolling through job boards, \"Data Scientist\" can differ quite a bit. Some read more like a data engineer, focusing on pipelines and big data platforms. Some are closer to a data analyst, focusing on data cleaning and dashboarding. And as of late, there are many that are similar to software or ML engineering, focusing on object-oriented programming, building applications, deploying models, and sometimes even web development.\n![Image by Author](../images/000.png)\nImage by Author\nAnd there are those who expect all of this and more, thus, the \"full-stack Data Scientist\". With this in mind, data scientists should consider looking to go beyond developing models in a notebook and expand their skillset to other areas like ML Ops. As [Pau Labarta Bajo](https://datamachines.xyz/) says: \"**ML models inside Jupyter notebooks have a business value of $𝟬.𝟬𝟬\".**\n**This article will go over how data scientists can successfully deploy their machine learning models from notebooks to fully productionized APIs by using FastAPI and Docker.**\n### Thoughts on the \"Full-Stack\" Data Scientist\nFirst, my personal opinion on the \"full-stack data scientist\". With all of these emerging expectations, it is important for us to learn and be comfortable with other skills that we may not have learned in our education or early career. However, the expectation seems to be to master all of these skills, on top of keeping up with traditional data science. And while there are a few out there who are capable of this, it is not feasible for most of us.\nI don't believe that becoming a full-stack data scientist means mastering every one of these skills, technologies, etc. **I think that a full-stack data scientist is about being able to wear all of the hats in the data science lifecycle through continuous learning and development.**\nWhile it may not be my expertise, I should be able to collaborate with data engineers to optimize pipelines. And while I am much more comfortable with developing models, I should be able to wear my \"ML Engineer\" hat and help get a model into deployment. A great data scientist will always have their niches, but will also have a working knowledge of other areas and can quickly learn new skills if and when they need to.\n* * *\n## Model Development\nFirst, for our example, we need to develop a model. **Since this article focuses on model deployment, we will not worry about the performance of the model.** **Instead, we will build a simple model with limited features to focus on learning model deployment.**\nIn this example, we will predict a data professional's salary based on a few features, such as experience, job title, company size, etc.\n_See data here:_[_https://www.kaggle.com/datasets/ruchi798/data-science-job-salaries_](https://www.kaggle.com/datasets/ruchi798/data-science-job-salaries) _(CC0: Public Domain). I slightly modified the data to reduce the number of options for certain features._\npython\n\n```\n#import packages for data manipulation\nimport pandas as pd\nimport numpy as np\n\n#import packages for machine learning\nfrom sklearn import linear_model\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import OneHotEncoder, OrdinalEncoder\nfrom sklearn.metrics import mean_squared_error, r2_score\n\n#import packages for data management\nimport joblib\n```\n\nFirst, let's take a look at the data.\n![Image by Author](../images/001.png)\nImage by Author\nSince all of our features are categorical, we will use encoding to transform our data to numerical. Below, we use ordinal encoders to encode experience level and company size. These are ordinal because they represent some kind of progression (1 = entry level, 2 = mid-level, etc.).\nFor job title and employment type, we will create a dummy variables for each option (note we drop the first to avoid multicollinearity).\npython\n\n```\n#use ordinal encoder to encode experience level\nencoder = OrdinalEncoder(categories=[['EN', 'MI', 'SE', 'EX']])\nsalary_data['experience_level_encoded'] = encoder.fit_transform(salary_data[['experience_level']])\n\n#use ordinal encoder to encode company size\nencoder = OrdinalEncoder(categories=[['S', 'M', 'L']])\nsalary_data['company_size_encoded'] = encoder.fit_transform(salary_data[['company_size']])\n\n#encode employmeny type and job title using dummy columns\nsalary_data = pd.get_dummies(salary_data, columns = ['employment_type', 'job_title'], drop_first = True, dtype = int)\n\n#drop original columns\nsalary_data = salary_data.drop(columns = ['experience_level', 'company_size'])\n```\n\nNow that we have transformed our model inputs, we can create our training and test sets. We will input these features into a simple linear regression model to predict the employee's salary.\npython\n\n```\n#define independent and dependent features\nX = salary_data.drop(columns = 'salary_in_usd')\ny = salary_data['salary_in_usd']\n\n#split between training and testing sets\nX_train, X_test, y_train, y_test = train_test_split(\n  X, y, random_state = 104, test_size = 0.2, shuffle = True)\n\n#fit linear regression model\nregr = linear_model.LinearRegression()\nregr.fit(X_train, y_train)\n\n#make predictions\ny_pred = regr.predict(X_test)\n\n#print the coefficients\nprint(\"Coefficients: n\", regr.coef_)\n\n#print the MSE\nprint(\"Mean squared error: %.2f\" % mean_squared_error(y_test, y_pred))\n\n#print the adjusted R2 value\nprint(\"R2: %.2f\" % r2_score(y_test, y_pred))\n```\n\nLet's see how our model did.\n![Image by Author](../images/002.png)\nImage by Author\nLooks like our R-squared is 0.27, yikes. A lot more work would need to be done with this model. We would likely need more data and additional information on the observations. But for the sake of this article, we will move forward and save our model.\npython\n\n```\n#save model using joblib\njoblib.dump(regr, 'lin_regress.sav')\n```\n\n* * *\n## Creating an API\nThere are several ways to deploy a model. One of those ways is with an API. **An API (Application Programming Interface) enables two pieces of software to communicate with each other.** There are several API architectures like SOAP, RPC, and REST APIs. We will use a REST API, which is the most popular and flexible architecture to access a service.\nFor our framework, we will use FastAPI (<https://fastapi.tiangolo.com/>), which is great for beginners as its fairly easy to use and has tons of documentation and examples.\nWith REST APIs, there are five methods that are commonly used: POST, GET, PUT, PATCH, and DELETE. These correspond to create, read, update, and delete operations. Our script below (Main.py) will follow these steps:\n  1. Initialize the FastAPI framework and define the request format.\n  2. Download the model.\n  3. Create a GET endpoint to retrieve the model.\n  4. Create a POST endpoint to allow the user to send it new data and create a prediction.\n  5. Define the host IP and port (location to operate the API).\n\n\npython\n\n```\nimport uvicorn\nimport pandas as pd\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\nimport joblib\n\n# Initialize FastAPI\napp = FastAPI()\n\n# Define the request body format for predictions\nclass PredictionFeatures(BaseModel):\n    experience_level_encoded: float\n    company_size_encoded: float\n    employment_type_PT: int\n    job_title_Data_Engineer: int\n    job_title_Data_Manager: int\n    job_title_Data_Scientist: int\n    job_title_Machine_Learning_Engineer: int\n\n# Global variable to store the loaded model\nmodel = None\n\n# Download the model\ndef download_model():\n    global model\n    model = joblib.load('lin_regress.sav')\n\n# Download the model immediately when the script runs\ndownload_model()\n\n# API Root endpoint\n@app.get(\"/\")\nasync def index():\n    return {\"message\": \"Welcome to the Data Science Income API. Use the /predict feature to predict your income.\"}\n\n# Prediction endpoint\n@app.post(\"/predict\")\nasync def predict(features: PredictionFeatures):\n\n    # Create input DataFrame for prediction\n    input_data = pd.DataFrame([{\n        \"experience_level_encoded\": features.experience_level_encoded,\n        \"company_size_encoded\": features.company_size_encoded,\n        \"employment_type_PT\": features.employment_type_PT,\n        \"job_title_Data Engineer\": features.job_title_Data_Engineer,\n        \"job_title_Data Manager\": features.job_title_Data_Manager,\n        \"job_title_Data Scientist\": features.job_title_Data_Scientist,\n        \"job_title_Machine Learning Engineer\": features.job_title_Machine_Learning_Engineer\n    }])\n\n    # Predict using the loaded model\n    prediction = model.predict(input_data)[0]\n\n    return {\n        \"Salary (USD)\": prediction\n    }\n\nif __name__ == \"__main__\":\n    uvicorn.run(app, host=\"0.0.0.0\", port=8000)\n```\n\nNow let's use the command line to test the API. First, change the directory to your project. Then, run the API using uvicorn.\nshell\n\n```\ncd \"C:UsersadaviOneDriveDesktopSalary Model\"\npy -m uvicorn main:app --reload\n```\n\nThe command line gives me a link to follow. I am then greeted with the message from the GET endpoint. Nice!\n![Image by Author](../images/003.png)\nImage by Author\nLastly, let's create a test script to submit new data and retrieve a prediction. Using the requests library, we define the URL and submit a new observation.\npython\n\n```\nimport requests\n\nurl = 'http://127.0.0.1:8000/predict'\n\n#dummy data to test API\ndata = {\"experience_level_encoded\": 3.0,\n        \"company_size_encoded\": 3.0,\n        \"employment_type_PT\": 0,\n        \"job_title_Data_Engineer\": 0,\n        \"job_title_Data_Manager\": 1,\n        \"job_title_Data_Scientist\": 0,\n        \"job_title_Machine_Learning_Engineer\": 0}\n\n#make a POST request to the API\nresponse = requests.post(url, json=data)\n\n#print response\nresponse.json()\n```\n\n![Image by Author](../images/004.png)\nImage by Author\nThe prediction is then returned in JSON format thanks to the POST endpoint. Great, we have a functioning API!\n* * *\n## Deploying Model Using Docker\n### What is Docker?\nNow we have a way to interact with our model, but the model is still not deployed. Let's say we have a team of 20 people, all of whom we want to have the API running on their computer. This is likely to be a headache. Replicating data science applications can be challenging as there are a number of roadblocks, such as different operating systems, dependencies, tech stacks, etc.\nThis is where Docker comes in. Docker is a platform that enables developers to package their applications and all of their dependencies in \"containers\". Anyone who has access to a container can run the application without worrying about downloading the correct versions of packages, changing operating systems, etc. Docker containers are also very fast and lightweight, giving an advantage over virtual environments or machines.\nDownload Docker Desktop here: <https://www.docker.com/>\n### Creating a DockerFile and Image\nBefore we create a container, we must first create an image. A Docker image is a snapshot of the application and its dependencies. It basically outlines the instructions for the container.\nTo create an image, you must create a Dockerfile (<https://docs.docker.com/reference/dockerfile/>). The Dockerfile is a text-based document that is stored inside the project and provides the instructions on how to assemble the image. The Dockerfile cannot be a .txt file. It must have no extension. **The easiest way to create a Dockerfile is through VSCode. Simply add a new file, and name it \"Dockerfile\".**\nI built the following Dockerfile using their beginner documentation. It follows these steps:\n  1. Install python 3.9.\n  2. Create a new directory and copy the project files.\n  3. Install the necessary packages using requirements.txt.\n  4. Specify the port (8000).\n  5. Run the application.\n\n\nbash\n\n```\n# A Dockerfile is a text document that contains all the commands\n# a user could call on the command line to assemble an image.\n\nFROM python:3.9.4-buster\n\n# Our Debian with python is now installed.\n\nRUN mkdir build\n\n# We create folder named build for our stuff.\n\nWORKDIR /build\n\n# Now we just want to our WORKDIR to be /build\n\nCOPY . .\n\n# FROM [path to files from the folder we run docker run]\n# TO [current WORKDIR]\n# We copy our files (files from .dockerignore are ignored)\n# to the WORKDIR\n\nRUN pip install --no-cache-dir -r requirements.txt\n\n# OK, now we pip install our requirements\n\nEXPOSE 8000\n\n# Instruction informs Docker that the container listens on port 8000\n\nWORKDIR /build/app\n\n# Now we just want to our WORKDIR to be /build/app for simplicity\n\nCMD [\"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]\n\n# This command runs our uvicorn server\n```\n\nNow that we have our Dockerfile, we can create the image with the following command. The name of the image will be \"apiserver\".\nbash\n\n```\n#build docker image\ndocker build . -t apiserver\n```\n\nIf we navigate to Docker Desktop, we can see that the image was successfully created.\n![Image by Author](../images/005.png)\nImage by Author\n### Creating a Docker Container\nNow that we have an image, creating the container is very simple. Once we run the image with a few instructions, the container is created. Below, we run the image and specify the port.\nbash\n\n```\n#run docker image\n#acces at http://localhost:8000\ndocker run --rm -it  -p 8000:8000/tcp apiserver:latest\n```\n\nIf we navigate back to Docker Desktop again, we can see the container. Docker gives containers random names, which can become difficult to track. If you develop many applications, it is useful to rename them.\n![Image by Author](../images/006.png)\nImage by Author\nThe model is now deployed! Going back to our team of 20, all they need is Docker installed on their machine and access to our container. Then they can run the container and use the API as needed.\n* * *\n## Conclusion\nIn conclusion, with new expectations for data scientists, it is vital to learn other skills like software engineering and ML Ops. The need for \"full-stack data scientists\" is growing as organizations need those that can engage in all stages of the data science lifecycle.\nTaking machine learning models out of notebooks and into production is a great first step to become a full-stack data scientist. By using tools like FastAPI and Docker, you can share the hard work it took to build your model by allowing others to use it too.\n* * *\n_I hope you have enjoyed my article! Please feel free to comment, ask questions, or request other topics._\n_Connect with me on LinkedIn:_[_https://www.linkedin.com/in/alexdavis2020/_](https://www.linkedin.com/in/alexdavis2020/)\n## Register Now to Continue Reading\n### Unlock Towards Data Science with a Free Account\nAll articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.\nRegister for freeSign in to your account\n* * *\n![](../images/body-d879ad8ec57b4f95a4cd.jpeg)\nWritten By\n## [Alex Davis](https://towardsdatascience.com/author/adavis08/)\n[See all from Alex Davis](https://towardsdatascience.com/author/adavis08/)\n[API](https://towardsdatascience.com/tag/api/)[Data Science](https://towardsdatascience.com/tag/data-science/)[Docker](https://towardsdatascience.com/tag/docker/)[Mlops](https://towardsdatascience.com/tag/mlops/)[Regression](https://towardsdatascience.com/tag/regression/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F&title=Journey%20to%20Full-Stack%20Data%20Scientist%3A%20Model%20Deployment)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F&title=Journey%20to%20Full-Stack%20Data%20Scientist%3A%20Model%20Deployment)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F&text=Journey%20to%20Full-Stack%20Data%20Scientist%3A%20Model%20Deployment)\n\n\nTowards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.\n[Write for TDS](https://towardsdatascience.com/submissions/)\n## Related Articles\n  * [![](../images/007.png)](https://towardsdatascience.com/pandas-groupby-vs-sql-group-by-39ccd7d2b779/)\n## [Pandas Groupby vs SQL Group By](https://towardsdatascience.com/pandas-groupby-vs-sql-group-by-39ccd7d2b779/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nExplained with examples.\n[Soner Yıldırım](https://towardsdatascience.com/author/sonery/)\nOctober 28, 20205 min read\n  * [![](../images/008.jpeg)](https://towardsdatascience.com/market-basket-analysis-with-pandas-246fb8ee10a5/)\n## [Market Basket Analysis with Pandas](https://towardsdatascience.com/market-basket-analysis-with-pandas-246fb8ee10a5/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nWhat items are usually bought together?\n[Soner Yıldırım](https://towardsdatascience.com/author/sonery/)\nOctober 7, 20205 min read\n  * [![](../images/009.png)](https://towardsdatascience.com/how-i-built-a-deep-learning-powered-emoji-slackbot-5d3e59b76d0/)\n## [How I Built a Deep Learning Powered Emoji Slackbot 😄 😖 😀](https://towardsdatascience.com/how-i-built-a-deep-learning-powered-emoji-slackbot-5d3e59b76d0/)\n[Deep Learning](https://towardsdatascience.com/category/artificial-intelligence/deep-learning/)\nAnd Why the Hard Part of AI has Nothing to Do with AI\n[Chenggang Wu](https://towardsdatascience.com/author/cgwu0530/)\nJuly 23, 20204 min read\n  * [![](../images/010.jpg)](https://towardsdatascience.com/what-i-learned-setting-up-storage-for-a-machine-learning-project-7ae1e5668762/)\n## [What I Learned Setting up Storage for a Machine Learning Project](https://towardsdatascience.com/what-i-learned-setting-up-storage-for-a-machine-learning-project-7ae1e5668762/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nA journey towards a fast and scalable storage solution\n[Stefan Pfaffel](https://towardsdatascience.com/author/stfsy/)\nFebruary 8, 20216 min read\n  * [![](../images/011.jpg)](https://towardsdatascience.com/gpu-optional-python-be36a02b634d/)\n## [GPU-Optional Python](https://towardsdatascience.com/gpu-optional-python-be36a02b634d/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nWrite code that exploits a GPU when available and desirable, but that runs fine on your CPUs when not\n[Carl Kadie](https://towardsdatascience.com/author/carlmkadie/)\nNovember 11, 20206 min read\n  * [![](../images/012.png)](https://towardsdatascience.com/regional-and-online-learnable-fields-cbb18b74e01e/)\n## [Regional and Online Learnable Fields](https://towardsdatascience.com/regional-and-online-learnable-fields-cbb18b74e01e/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nA new, not so new, clustering algorithm\n[Johannes Wohlenberg](https://towardsdatascience.com/author/johanneswohlenberg/)\nJuly 13, 20216 min read\n  * [![](../images/013.jpg)](https://towardsdatascience.com/explainable-ai-xai-with-class-maps-d0e137a91d2c/)\n## [Explainable AI (XAI) with Class Maps](https://towardsdatascience.com/explainable-ai-xai-with-class-maps-d0e137a91d2c/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nIntroducing a novel visual tool for explaining the results of classification algorithms, with examples in R and Python.\n[Nura Kawa](https://towardsdatascience.com/author/nurakawa/)\nFebruary 2, 202213 min read\n  * [![](../images/014.png)](https://towardsdatascience.com/discovering-powerful-data-the-guardian-news-api-into-python-for-nlp-1829b568fb0f/)\n## [Discovering Powerful Data: An NLP Goldmine](https://towardsdatascience.com/discovering-powerful-data-the-guardian-news-api-into-python-for-nlp-1829b568fb0f/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nUsing the Free Guardian News API with Python to Unlock a Plethora of Tagged Data\n[Adrian Causby](https://towardsdatascience.com/author/adrian.causby15/)\nFebruary 17, 20221 min read\n  * [![](../images/015.png)](https://towardsdatascience.com/hierarchical-clustering-and-k-means-clustering-on-country-data-84b2bf54d282/)\n## [Hierarchical Clustering and K-means Clustering on Country Data](https://towardsdatascience.com/hierarchical-clustering-and-k-means-clustering-on-country-data-84b2bf54d282/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nAn overview of two clustering techniques and comparing the results\n[Andrea Gustafsen](https://towardsdatascience.com/author/andreagustafsen/)\nMay 4, 202214 min read\n\n\n  * [YouTube](https://www.youtube.com/c/TowardsDataScience)\n  * [X](https://x.com/TDataScience)\n  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)\n  * [Threads](https://www.threads.net/@towardsdatascience)\n  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)\n\n\n[](https://towardsdatascience.com/)\nYour home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.\nSubscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)\n© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)\n####  Recommended Articles \nClose\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607686/image?etag=1782161464448&width=500&height=500) How Relevance Models Foreshadowed Transformers for NLP ](https://towardsdatascience.com/how-relevance-models-foreshadowed-transformers-for-nlp/ \"How Relevance Models Foreshadowed Transformers for NLP\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500) Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1 ](https://towardsdatascience.com/javascript-fatigue-you-dont-need-js-to-build-chatgpt/ \"Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500) The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs ](https://towardsdatascience.com/subset-sum-problem-solved-in-linear-time-for-dense-enough-inputs/ \"The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500) Does Your Company Have a Data Strategy? ](https://towardsdatascience.com/does-your-company-have-a-data-strategy-e8944dbec4ae/ \"Does Your Company Have a Data Strategy?\")\n\n\n",
  "metadata": {
    "title": "Journey to Full-Stack Data Scientist: Model Deployment | Towards Data Science",
    "description": "An introduction to productionizing a machine learning model using APIs and Docker.",
    "keywords": null,
    "author": "Alex Davis",
    "og:locale": "en_US",
    "og:type": "article",
    "og:title": "Journey to Full-Stack Data Scientist: Model Deployment",
    "og:description": "An introduction to productionizing a machine learning model using APIs and Docker.",
    "og:url": "https://towardsdatascience.com/journey-to-full-stack-data-scientist-model-deployment-f385f244ec67/",
    "og:site_name": "Towards Data Science",
    "og:image": "https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/14UmVrOm7F2yBkxfP3BMjmA.png",
    "og:image:width": "1502",
    "og:image:height": "802",
    "og:image:type": "image/png",
    "twitter:card": "summary_large_image",
    "twitter:site": "@TDataScience",
    "twitter:creator": "@TDataScience",
    "twitter:label1": "Written by",
    "twitter:data1": "Alex Davis",
    "twitter:label2": "Est. reading time",
    "twitter:data2": "9 min read",
    "article:published_time": "2025-01-04T15:02:03.000Z",
    "article:modified_time": "2025-01-04T15:02:03.000Z",
    "article:author": "https://towardsdatascience.com/author/adavis08/"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_000.txt",
      "chars": 376
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_001.txt",
      "chars": 682
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_002.txt",
      "chars": 649
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_003.txt",
      "chars": 62
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_004.txt",
      "chars": 1763
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_005.txt",
      "chars": 77
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_006.txt",
      "chars": 462
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_007.txt",
      "chars": 865
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_008.txt",
      "chars": 48
    },
    {
      "language": "txt",
      "file": "attachments/f7c049d9569d/code/block_009.txt",
      "chars": 105
    }
  ],
  "images": [
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607686/image?etag=1782161464448&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607686/image?etag=1782161464448&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:52:05Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:52:05Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:52:05Z"
    },
    {
      "status": "http_404",
      "url": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500",
      "src": "https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500",
      "alt": "",
      "downloaded": false,
      "discovery": "saved_markdown",
      "retried_at": "2026-10-20T17:52:05Z"
    }
  ],
  "audio": [],
  "videos": [
    {
      "url": "https://www.youtube.com/c/TowardsDataScience",
      "kind": "channel_reference",
      "status": "reference_only"
    }
  ],
  "links": [
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/latest/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/editors-pick/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/the-variable/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/shipai/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/submissions/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/data-science/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/adavis08/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/api/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/data-science/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/docker/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/mlops/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/tag/regression/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/pandas-groupby-vs-sql-group-by-39ccd7d2b779/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/artificial-intelligence/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/sonery/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/market-basket-analysis-with-pandas-246fb8ee10a5/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/how-i-built-a-deep-learning-powered-emoji-slackbot-5d3e59b76d0/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/category/artificial-intelligence/deep-learning/"
    },
    {
      "type": "internal",
      "href": "https://towardsdatascience.com/author/cgwu0530/"
    },
    {
      "type": "external",
      "href": "https://write.insightmediagroup.io"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/company/towards-data-science/"
    },
    {
      "type": "external",
      "href": "https://x.com/TDataScience"
    },
    {
      "type": "external",
      "href": "https://datamachines.xyz/"
    },
    {
      "type": "external",
      "href": "https://www.kaggle.com/datasets/ruchi798/data-science-job-salaries"
    },
    {
      "type": "external",
      "href": "https://fastapi.tiangolo.com/"
    },
    {
      "type": "external",
      "href": "https://www.docker.com/"
    },
    {
      "type": "external",
      "href": "https://docs.docker.com/reference/dockerfile/"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/in/alexdavis2020/"
    },
    {
      "type": "external",
      "href": "https://www.facebook.com/sharer/sharer.php?title=Journey+to+Full-Stack+Data+Scientist%3A+Model+Deployment&u=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F"
    },
    {
      "type": "external",
      "href": "https://www.linkedin.com/shareArticle?mini=true&title=Journey+to+Full-Stack+Data+Scientist%3A+Model+Deployment&url=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F"
    },
    {
      "type": "external",
      "href": "https://x.com/share?text=Journey+to+Full-Stack+Data+Scientist%3A+Model+Deployment&url=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F"
    },
    {
      "type": "external",
      "href": "https://www.youtube.com/c/TowardsDataScience"
    },
    {
      "type": "external",
      "href": "https://www.threads.net/@towardsdatascience"
    },
    {
      "type": "external",
      "href": "https://bsky.app/profile/towardsdatascience.com"
    },
    {
      "type": "external",
      "href": "https://thenewstack.io"
    },
    {
      "type": "external",
      "href": "https://roadmap.sh"
    },
    {
      "type": "external",
      "href": "https://insightmediagroup.io/creator-network"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-20T17:27:47Z",
  "source_url": "https://towardsdatascience.com/journey-to-full-stack-data-scientist-model-deployment-f385f244ec67/",
  "delivery_status": "unverified",
  "discovery_status": "http_403",
  "missing_reason": "源页面未重新核验:http_403；已按保留正文补采图片",
  "repair_error": "",
  "actual_video_files": [],
  "expected_saved_body_images": 4,
  "image_discovery": "saved_markdown",
  "source_body_markdown": "![Revisit consent button](https://cdn-cookieyes.com/assets/images/revisit.svg)\nWe value your privacy\nWe use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking \"Accept All\", you consent to our use of cookies.\nCustomise Reject All Accept All\nCustomise Consent Preferences ![](https://cdn-cookieyes.com/assets/images/close.svg)\nWe use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.\nThe cookies that are categorised as \"Necessary\" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... Show more\nNecessaryAlways Active\nNecessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.\n  * Cookie\nBCTempID\n  * Duration\n10 minutes\n  * Description\nNo description available.\n\n\n  * Cookie\n__cf_bm\n  * Duration\n1 hour\n  * Description\nThis cookie, set by Cloudflare, is used to support Cloudflare Bot Management. \n\n\n  * Cookie\nAWSALBCORS\n  * Duration\n7 days\n  * Description\nAmazon Web Services set this cookie for load balancing.\n\n\n  * Cookie\n_cfuvid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to track users across sessions to optimize user experience by maintaining session consistency and providing personalized services\n\n\n  * Cookie\nli_gc\n  * Duration\n6 months\n  * Description\nLinkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.\n\n\n  * Cookie\n__hssrc\n  * Duration\nsession\n  * Description\nThis cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.\n\n\n  * Cookie\n__hssc\n  * Duration\n1 hour\n  * Description\nHubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.\n\n\n  * Cookie\nwpEmojiSettingsSupports\n  * Duration\nsession\n  * Description\nWordPress sets this cookie when a user interacts with emojis on a WordPress site. It helps determine if the user's browser can display emojis properly.\n\n\n  * Cookie\nBCSessionID\n  * Duration\n1 year 1 month 4 days\n  * Description\nBlueconic sets this cookie as a unique identifier for the BlueConic profile.\n\n\n  * Cookie\n_octo\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nlogged_in\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\n__Secure-YEC\n  * Duration\npast\n  * Description\nYouTube sets this cookie to stores the user's video player preferences using embedded YouTube video\n\n\n  * Cookie\n__eoi\n  * Duration\n6 months\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTGCORS\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\nlogin-status-p\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nAWSALBTG\n  * Duration\n7 days\n  * Description\nNo description available.\n\n\n  * Cookie\ncsrf_token\n  * Duration\nsession\n  * Description\nNo description available.\n\n\n  * Cookie\ntoken_v2\n  * Duration\n1 day\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nD\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nPHPSESSID\n  * Duration\nsession\n  * Description\nThis cookie is native to PHP applications. The cookie stores and identifies a user's unique session ID to manage user sessions on the website. The cookie is a session cookie and will be deleted when all the browser windows are closed.\n\n\n  * Cookie\nVISITOR_PRIVACY_METADATA\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to store the user's cookie consent state for the current domain. \n\n\n  * Cookie\ncookietest\n  * Duration\nsession\n  * Description\nThe cookietest cookie is typically used to determine whether the user's browser accepts cookies, essential for website functionality and user experience.\n\n\n  * Cookie\n__Host-airtable-session\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\n__Host-airtable-session.sig\n  * Duration\n1 year\n  * Description\nThis cookie is used to enable us to integrate the services of Airtable.\n\n\n  * Cookie\nm\n  * Duration\n1 year 1 month 4 days\n  * Description\nStripe sets this cookie for fraud prevention purposes. It identifies the device used to access the website, allowing the website to be formatted accordingly.\n\n\n  * Cookie\nBIGipServer*\n  * Duration\nsession\n  * Description\nMarketo sets this cookie to collect information about the user's online activity and build a profile about their interests to provide advertisements relevant to the user. \n\n\n  * Cookie\n__cfruid\n  * Duration\nsession\n  * Description\nCloudflare sets this cookie to identify trusted web traffic.\n\n\n  * Cookie\n_GRECAPTCHA\n  * Duration\n6 months\n  * Description\nGoogle Recaptcha service sets this cookie to identify bots to protect the website against malicious spam attacks.\n\n\n  * Cookie\n__Secure-YNID\n  * Duration\n6 months\n  * Description\nGoogle cookie used to protect user security and prevent fraud, especially during the login process.\n\n\n  * Cookie\ncookieyes-consent\n  * Duration\n1 year\n  * Description\nCookieYes sets this cookie to remember users' consent preferences so that their preferences are respected on subsequent visits to this site. It does not collect or store any personal information about the site visitors.\n\n\nFunctional\nFunctional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.\n  * Cookie\nlidc\n  * Duration\n1 day\n  * Description\nLinkedIn sets the lidc cookie to facilitate data center selection.\n\n\n  * Cookie\nbrw\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nbrwConsent\n  * Duration\n5 minutes\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nWMF-Uniq\n  * Duration\n1 year\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nloom_anon_comment\n  * Duration\n1 year\n  * Description\nNo description available.\n\n\n  * Cookie\nloom_referral_video\n  * Duration\nsession\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nVISITOR_INFO1_LIVE\n  * Duration\n6 months\n  * Description\nA cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface.\n\n\n  * Cookie\nyt-remote-connected-devices\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nytidb::LAST_RESULT_ENTRY_KEY\n  * Duration\nNever Expires\n  * Description\nThe cookie ytidb::LAST_RESULT_ENTRY_KEY is used by YouTube to store the last search result entry that was clicked by the user. This information is used to improve the user experience by providing more relevant search results in the future.\n\n\n  * Cookie\nyt-remote-device-id\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to store the user's video preferences using embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-name\n  * Duration\nsession\n  * Description\nThe yt-remote-session-name cookie is used by YouTube to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\nyt-remote-fast-check-period\n  * Duration\nsession\n  * Description\nThe yt-remote-fast-check-period cookie is used by YouTube to store the user's video player preferences for embedded YouTube videos.\n\n\n  * Cookie\nyt-remote-session-app\n  * Duration\nsession\n  * Description\nThe yt-remote-session-app cookie is used by YouTube to store user preferences and information about the interface of the embedded YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-available\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-available cookie is used to store the user's preferences regarding whether casting is available on their YouTube video player.\n\n\n  * Cookie\nyt-remote-cast-installed\n  * Duration\nsession\n  * Description\nThe yt-remote-cast-installed cookie is used to store the user's video player preferences using embedded YouTube video.\n\n\n  * Cookie\ncp_session\n  * Duration\n3 months\n  * Description\nCodepen sets this cookie for Help systems found in the website.\n\n\n  * Cookie\nloid\n  * Duration\n1 year 1 month 4 days\n  * Description\nThis cookie is set by the Reddit. The cookie enables the sharing of content from the website onto the social media platform.\n\n\nAnalytics\nAnalytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.\n  * Cookie\n__hstc\n  * Duration\n6 months\n  * Description\nHubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).\n\n\n  * Cookie\nhubspotutk\n  * Duration\n6 months\n  * Description\nHubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.\n\n\n  * Cookie\n_ga\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.\n\n\n  * Cookie\n_ga_*\n  * Duration\n1 year 1 month 4 days\n  * Description\nGoogle Analytics sets this cookie to store and count page views.\n\n\n  * Cookie\n__Host-psifi.analyticsTrace\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n__Host-psifi.analyticsTraceV2\n  * Duration\n6 hours\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\n_gh_sess\n  * Duration\nsession\n  * Description\nGitHub sets this cookie for temporary application and framework state between pages like what step the user is on in a multiple step form.\n\n\n  * Cookie\nYSC\n  * Duration\nsession\n  * Description\nYSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages.\n\n\n  * Cookie\najs_anonymous_id\n  * Duration\n1 year\n  * Description\nThis cookie is set by Segment to count the number of people who visit a certain site by tracking if they have visited before.\n\n\n  * Cookie\nvuid\n  * Duration\n1 year 1 month 4 days\n  * Description\nVimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. \n\n\nPerformance\nPerformance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.\n  * Cookie\nAWSALB\n  * Duration\n7 days\n  * Description\nAWSALB is an application load balancer cookie set by Amazon Web Services to map the session to the target.\n\n\n  * Cookie\nacq\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nacq.sig\n  * Duration\npast\n  * Description\nDescription is currently not available.\n\n\n  * Cookie\nptc\n  * Duration\n2 years\n  * Description\nNo description available.\n\n\nAdvertisement\nAdvertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.\n  * Cookie\nmuc_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to collect user behaviour and interaction data to optimize the website.\n\n\n  * Cookie\nguest_id_marketing\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\nguest_id_ads\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor.\n\n\n  * Cookie\npersonalization_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to integrate and share features for social media and also store information about how the user uses the website, for tracking and targeting.\n\n\n  * Cookie\nguest_id\n  * Duration\n1 year 1 month 4 days\n  * Description\nTwitter sets this cookie to identify and track the website visitor. It registers if a user is signed in to the Twitter platform and collects information about ad preferences.\n\n\n  * Cookie\nbcookie\n  * Duration\n1 year\n  * Description\nLinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.\n\n\n  * Cookie\n__Secure-ROLLOUT_TOKEN\n  * Duration\n6 months\n  * Description\nYouTube sets this cookie to manage feature rollout and experimentation. It helps Google control which new features or interface changes are shown to users as part of testing and staged rollouts, ensuring consistent experience for a given user during an experiment.\n\n\n  * Cookie\nyt.innertube::nextId\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nyt.innertube::requests\n  * Duration\nNever Expires\n  * Description\nYouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.\n\n\n  * Cookie\nsession_tracker\n  * Duration\nsession\n  * Description\nThis cookie is set by the Reddit. This cookie is used to identify trusted web traffic. It also helps in adverstising on the website.\n\n\n  * Cookie\nedgebucket\n  * Duration\nsession\n  * Description\nReddit sets this cookie to save the information about a log-on Reddit user, for the purpose of advertisement recommendations and updating the content.\n\n\n  * Cookie\ndid\n  * Duration\n1 year\n  * Description\nArbor sets this cookie to show targeted ads to site visitors.This cookie expires after 2 months or 1 year.\n\n\nUncategorised\nOther uncategorised cookies are those that are being analysed and have not been classified into a category as yet.\nNo cookies to display.\nReject All Save My Preferences Accept All\nToggle Search[](https://towardsdatascience.com/)Toggle Mobile Navigation\n[](https://towardsdatascience.com/)\nPublish AI, ML & data-science insights to a global community of data professionals.\nSign in[Submit an Article](https://write.insightmediagroup.io)\n[Latest](https://towardsdatascience.com/latest/)[Editor's Picks](https://towardsdatascience.com/tag/editors-pick/)[Newsletter](https://towardsdatascience.com/tag/the-variable/)[ShipAI](https://towardsdatascience.com/shipai/)\n* * *\n[Write For TDS](https://towardsdatascience.com/submissions/)\n[LinkedIn](https://www.linkedin.com/company/towards-data-science/)[X](https://x.com/TDataScience)Toggle Search\n[Data Science](https://towardsdatascience.com/category/data-science/)\n# Journey to Full-Stack Data Scientist: Model Deployment\nAn introduction to productionizing a machine learning model using APIs and Docker.\n[Alex Davis](https://towardsdatascience.com/author/adavis08/)\nJanuary 4, 20259 min read\n### An introduction to productionizing machine learning models using APIs and Docker.\n## Growing Responsibilities of Data Scientists\nThe title of data scientist is ever-changing and often vague. It usually involves one who is fluent in mathematics, programming, and machine learning. They spend time cleaning data, building models, fine-tuning, and conducting experimentation. They must also have great communication skills, a good grasp on their domain, and other soft skills.\nHowever, this is not always exactly the case. If you spend enough time scrolling through job boards, \"Data Scientist\" can differ quite a bit. Some read more like a data engineer, focusing on pipelines and big data platforms. Some are closer to a data analyst, focusing on data cleaning and dashboarding. And as of late, there are many that are similar to software or ML engineering, focusing on object-oriented programming, building applications, deploying models, and sometimes even web development.\n![Image by Author](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/14UmVrOm7F2yBkxfP3BMjmA.png)\nImage by Author\nAnd there are those who expect all of this and more, thus, the \"full-stack Data Scientist\". With this in mind, data scientists should consider looking to go beyond developing models in a notebook and expand their skillset to other areas like ML Ops. As [Pau Labarta Bajo](https://datamachines.xyz/) says: \"**ML models inside Jupyter notebooks have a business value of $𝟬.𝟬𝟬\".**\n**This article will go over how data scientists can successfully deploy their machine learning models from notebooks to fully productionized APIs by using FastAPI and Docker.**\n### Thoughts on the \"Full-Stack\" Data Scientist\nFirst, my personal opinion on the \"full-stack data scientist\". With all of these emerging expectations, it is important for us to learn and be comfortable with other skills that we may not have learned in our education or early career. However, the expectation seems to be to master all of these skills, on top of keeping up with traditional data science. And while there are a few out there who are capable of this, it is not feasible for most of us.\nI don't believe that becoming a full-stack data scientist means mastering every one of these skills, technologies, etc. **I think that a full-stack data scientist is about being able to wear all of the hats in the data science lifecycle through continuous learning and development.**\nWhile it may not be my expertise, I should be able to collaborate with data engineers to optimize pipelines. And while I am much more comfortable with developing models, I should be able to wear my \"ML Engineer\" hat and help get a model into deployment. A great data scientist will always have their niches, but will also have a working knowledge of other areas and can quickly learn new skills if and when they need to.\n* * *\n## Model Development\nFirst, for our example, we need to develop a model. **Since this article focuses on model deployment, we will not worry about the performance of the model.** **Instead, we will build a simple model with limited features to focus on learning model deployment.**\nIn this example, we will predict a data professional's salary based on a few features, such as experience, job title, company size, etc.\n_See data here:_[_https://www.kaggle.com/datasets/ruchi798/data-science-job-salaries_](https://www.kaggle.com/datasets/ruchi798/data-science-job-salaries) _(CC0: Public Domain). I slightly modified the data to reduce the number of options for certain features._\npython\n\n```\n#import packages for data manipulation\nimport pandas as pd\nimport numpy as np\n\n#import packages for machine learning\nfrom sklearn import linear_model\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import OneHotEncoder, OrdinalEncoder\nfrom sklearn.metrics import mean_squared_error, r2_score\n\n#import packages for data management\nimport joblib\n```\n\nFirst, let's take a look at the data.\n![Image by Author](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/1ww0uUrJKZq8NRBXpQ3-KoA.png)\nImage by Author\nSince all of our features are categorical, we will use encoding to transform our data to numerical. Below, we use ordinal encoders to encode experience level and company size. These are ordinal because they represent some kind of progression (1 = entry level, 2 = mid-level, etc.).\nFor job title and employment type, we will create a dummy variables for each option (note we drop the first to avoid multicollinearity).\npython\n\n```\n#use ordinal encoder to encode experience level\nencoder = OrdinalEncoder(categories=[['EN', 'MI', 'SE', 'EX']])\nsalary_data['experience_level_encoded'] = encoder.fit_transform(salary_data[['experience_level']])\n\n#use ordinal encoder to encode company size\nencoder = OrdinalEncoder(categories=[['S', 'M', 'L']])\nsalary_data['company_size_encoded'] = encoder.fit_transform(salary_data[['company_size']])\n\n#encode employmeny type and job title using dummy columns\nsalary_data = pd.get_dummies(salary_data, columns = ['employment_type', 'job_title'], drop_first = True, dtype = int)\n\n#drop original columns\nsalary_data = salary_data.drop(columns = ['experience_level', 'company_size'])\n```\n\nNow that we have transformed our model inputs, we can create our training and test sets. We will input these features into a simple linear regression model to predict the employee's salary.\npython\n\n```\n#define independent and dependent features\nX = salary_data.drop(columns = 'salary_in_usd')\ny = salary_data['salary_in_usd']\n\n#split between training and testing sets\nX_train, X_test, y_train, y_test = train_test_split(\n  X, y, random_state = 104, test_size = 0.2, shuffle = True)\n\n#fit linear regression model\nregr = linear_model.LinearRegression()\nregr.fit(X_train, y_train)\n\n#make predictions\ny_pred = regr.predict(X_test)\n\n#print the coefficients\nprint(\"Coefficients: n\", regr.coef_)\n\n#print the MSE\nprint(\"Mean squared error: %.2f\" % mean_squared_error(y_test, y_pred))\n\n#print the adjusted R2 value\nprint(\"R2: %.2f\" % r2_score(y_test, y_pred))\n```\n\nLet's see how our model did.\n![Image by Author](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/1LSPVauJjxXMIsoE9ZTN-2w.png)\nImage by Author\nLooks like our R-squared is 0.27, yikes. A lot more work would need to be done with this model. We would likely need more data and additional information on the observations. But for the sake of this article, we will move forward and save our model.\npython\n\n```\n#save model using joblib\njoblib.dump(regr, 'lin_regress.sav')\n```\n\n* * *\n## Creating an API\nThere are several ways to deploy a model. One of those ways is with an API. **An API (Application Programming Interface) enables two pieces of software to communicate with each other.** There are several API architectures like SOAP, RPC, and REST APIs. We will use a REST API, which is the most popular and flexible architecture to access a service.\nFor our framework, we will use FastAPI (<https://fastapi.tiangolo.com/>), which is great for beginners as its fairly easy to use and has tons of documentation and examples.\nWith REST APIs, there are five methods that are commonly used: POST, GET, PUT, PATCH, and DELETE. These correspond to create, read, update, and delete operations. Our script below (Main.py) will follow these steps:\n  1. Initialize the FastAPI framework and define the request format.\n  2. Download the model.\n  3. Create a GET endpoint to retrieve the model.\n  4. Create a POST endpoint to allow the user to send it new data and create a prediction.\n  5. Define the host IP and port (location to operate the API).\n\n\npython\n\n```\nimport uvicorn\nimport pandas as pd\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\nimport joblib\n\n# Initialize FastAPI\napp = FastAPI()\n\n# Define the request body format for predictions\nclass PredictionFeatures(BaseModel):\n    experience_level_encoded: float\n    company_size_encoded: float\n    employment_type_PT: int\n    job_title_Data_Engineer: int\n    job_title_Data_Manager: int\n    job_title_Data_Scientist: int\n    job_title_Machine_Learning_Engineer: int\n\n# Global variable to store the loaded model\nmodel = None\n\n# Download the model\ndef download_model():\n    global model\n    model = joblib.load('lin_regress.sav')\n\n# Download the model immediately when the script runs\ndownload_model()\n\n# API Root endpoint\n@app.get(\"/\")\nasync def index():\n    return {\"message\": \"Welcome to the Data Science Income API. Use the /predict feature to predict your income.\"}\n\n# Prediction endpoint\n@app.post(\"/predict\")\nasync def predict(features: PredictionFeatures):\n\n    # Create input DataFrame for prediction\n    input_data = pd.DataFrame([{\n        \"experience_level_encoded\": features.experience_level_encoded,\n        \"company_size_encoded\": features.company_size_encoded,\n        \"employment_type_PT\": features.employment_type_PT,\n        \"job_title_Data Engineer\": features.job_title_Data_Engineer,\n        \"job_title_Data Manager\": features.job_title_Data_Manager,\n        \"job_title_Data Scientist\": features.job_title_Data_Scientist,\n        \"job_title_Machine Learning Engineer\": features.job_title_Machine_Learning_Engineer\n    }])\n\n    # Predict using the loaded model\n    prediction = model.predict(input_data)[0]\n\n    return {\n        \"Salary (USD)\": prediction\n    }\n\nif __name__ == \"__main__\":\n    uvicorn.run(app, host=\"0.0.0.0\", port=8000)\n```\n\nNow let's use the command line to test the API. First, change the directory to your project. Then, run the API using uvicorn.\nshell\n\n```\ncd \"C:UsersadaviOneDriveDesktopSalary Model\"\npy -m uvicorn main:app --reload\n```\n\nThe command line gives me a link to follow. I am then greeted with the message from the GET endpoint. Nice!\n![Image by Author](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/1u3TwjDDIDqneVlwZwbt9FQ.png)\nImage by Author\nLastly, let's create a test script to submit new data and retrieve a prediction. Using the requests library, we define the URL and submit a new observation.\npython\n\n```\nimport requests\n\nurl = 'http://127.0.0.1:8000/predict'\n\n#dummy data to test API\ndata = {\"experience_level_encoded\": 3.0,\n        \"company_size_encoded\": 3.0,\n        \"employment_type_PT\": 0,\n        \"job_title_Data_Engineer\": 0,\n        \"job_title_Data_Manager\": 1,\n        \"job_title_Data_Scientist\": 0,\n        \"job_title_Machine_Learning_Engineer\": 0}\n\n#make a POST request to the API\nresponse = requests.post(url, json=data)\n\n#print response\nresponse.json()\n```\n\n![Image by Author](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/1fgRbzQYybq14C-0lDLekNA.png)\nImage by Author\nThe prediction is then returned in JSON format thanks to the POST endpoint. Great, we have a functioning API!\n* * *\n## Deploying Model Using Docker\n### What is Docker?\nNow we have a way to interact with our model, but the model is still not deployed. Let's say we have a team of 20 people, all of whom we want to have the API running on their computer. This is likely to be a headache. Replicating data science applications can be challenging as there are a number of roadblocks, such as different operating systems, dependencies, tech stacks, etc.\nThis is where Docker comes in. Docker is a platform that enables developers to package their applications and all of their dependencies in \"containers\". Anyone who has access to a container can run the application without worrying about downloading the correct versions of packages, changing operating systems, etc. Docker containers are also very fast and lightweight, giving an advantage over virtual environments or machines.\nDownload Docker Desktop here: <https://www.docker.com/>\n### Creating a DockerFile and Image\nBefore we create a container, we must first create an image. A Docker image is a snapshot of the application and its dependencies. It basically outlines the instructions for the container.\nTo create an image, you must create a Dockerfile (<https://docs.docker.com/reference/dockerfile/>). The Dockerfile is a text-based document that is stored inside the project and provides the instructions on how to assemble the image. The Dockerfile cannot be a .txt file. It must have no extension. **The easiest way to create a Dockerfile is through VSCode. Simply add a new file, and name it \"Dockerfile\".**\nI built the following Dockerfile using their beginner documentation. It follows these steps:\n  1. Install python 3.9.\n  2. Create a new directory and copy the project files.\n  3. Install the necessary packages using requirements.txt.\n  4. Specify the port (8000).\n  5. Run the application.\n\n\nbash\n\n```\n# A Dockerfile is a text document that contains all the commands\n# a user could call on the command line to assemble an image.\n\nFROM python:3.9.4-buster\n\n# Our Debian with python is now installed.\n\nRUN mkdir build\n\n# We create folder named build for our stuff.\n\nWORKDIR /build\n\n# Now we just want to our WORKDIR to be /build\n\nCOPY . .\n\n# FROM [path to files from the folder we run docker run]\n# TO [current WORKDIR]\n# We copy our files (files from .dockerignore are ignored)\n# to the WORKDIR\n\nRUN pip install --no-cache-dir -r requirements.txt\n\n# OK, now we pip install our requirements\n\nEXPOSE 8000\n\n# Instruction informs Docker that the container listens on port 8000\n\nWORKDIR /build/app\n\n# Now we just want to our WORKDIR to be /build/app for simplicity\n\nCMD [\"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]\n\n# This command runs our uvicorn server\n```\n\nNow that we have our Dockerfile, we can create the image with the following command. The name of the image will be \"apiserver\".\nbash\n\n```\n#build docker image\ndocker build . -t apiserver\n```\n\nIf we navigate to Docker Desktop, we can see that the image was successfully created.\n![Image by Author](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/1jkOHsGCCSDJKoY1e2EIHqQ.png)\nImage by Author\n### Creating a Docker Container\nNow that we have an image, creating the container is very simple. Once we run the image with a few instructions, the container is created. Below, we run the image and specify the port.\nbash\n\n```\n#run docker image\n#acces at http://localhost:8000\ndocker run --rm -it  -p 8000:8000/tcp apiserver:latest\n```\n\nIf we navigate back to Docker Desktop again, we can see the container. Docker gives containers random names, which can become difficult to track. If you develop many applications, it is useful to rename them.\n![Image by Author](https://assets.insightmediagroup.io/media/wp-content/uploads/2025/01/18KDdMzeExXsHw0IYm6DKgg.png)\nImage by Author\nThe model is now deployed! Going back to our team of 20, all they need is Docker installed on their machine and access to our container. Then they can run the container and use the API as needed.\n* * *\n## Conclusion\nIn conclusion, with new expectations for data scientists, it is vital to learn other skills like software engineering and ML Ops. The need for \"full-stack data scientists\" is growing as organizations need those that can engage in all stages of the data science lifecycle.\nTaking machine learning models out of notebooks and into production is a great first step to become a full-stack data scientist. By using tools like FastAPI and Docker, you can share the hard work it took to build your model by allowing others to use it too.\n* * *\n_I hope you have enjoyed my article! Please feel free to comment, ask questions, or request other topics._\n_Connect with me on LinkedIn:_[_https://www.linkedin.com/in/alexdavis2020/_](https://www.linkedin.com/in/alexdavis2020/)\n## Register Now to Continue Reading\n### Unlock Towards Data Science with a Free Account\nAll articles on Towards Data Science are available to registered users. Create a free account today to access this article and thousands more.\nRegister for freeSign in to your account\n* * *\n![](https://assets.insightmediagroup.io/media/1787657658380_gf1kgl.jpeg)\nWritten By\n## [Alex Davis](https://towardsdatascience.com/author/adavis08/)\n[See all from Alex Davis](https://towardsdatascience.com/author/adavis08/)\n[API](https://towardsdatascience.com/tag/api/)[Data Science](https://towardsdatascience.com/tag/data-science/)[Docker](https://towardsdatascience.com/tag/docker/)[Mlops](https://towardsdatascience.com/tag/mlops/)[Regression](https://towardsdatascience.com/tag/regression/)\nShare This Article\n  * [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F&title=Journey%20to%20Full-Stack%20Data%20Scientist%3A%20Model%20Deployment)\n  * [](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F&title=Journey%20to%20Full-Stack%20Data%20Scientist%3A%20Model%20Deployment)\n  * [](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Fjourney-to-full-stack-data-scientist-model-deployment-f385f244ec67%2F&text=Journey%20to%20Full-Stack%20Data%20Scientist%3A%20Model%20Deployment)\n\n\nTowards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.\n[Write for TDS](https://towardsdatascience.com/submissions/)\n## Related Articles\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2020/10/0mh8tWujATNawO_6x.png)](https://towardsdatascience.com/pandas-groupby-vs-sql-group-by-39ccd7d2b779/)\n## [Pandas Groupby vs SQL Group By](https://towardsdatascience.com/pandas-groupby-vs-sql-group-by-39ccd7d2b779/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nExplained with examples.\n[Soner Yıldırım](https://towardsdatascience.com/author/sonery/)\nOctober 28, 20205 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2020/10/1s9aLaVRWZIOmR2ZrQ9-XyQ-scaled.jpeg)](https://towardsdatascience.com/market-basket-analysis-with-pandas-246fb8ee10a5/)\n## [Market Basket Analysis with Pandas](https://towardsdatascience.com/market-basket-analysis-with-pandas-246fb8ee10a5/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nWhat items are usually bought together?\n[Soner Yıldırım](https://towardsdatascience.com/author/sonery/)\nOctober 7, 20205 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2020/07/1UBeATpG1wJB9zh7to5wAeQ.png)](https://towardsdatascience.com/how-i-built-a-deep-learning-powered-emoji-slackbot-5d3e59b76d0/)\n## [How I Built a Deep Learning Powered Emoji Slackbot 😄 😖 😀](https://towardsdatascience.com/how-i-built-a-deep-learning-powered-emoji-slackbot-5d3e59b76d0/)\n[Deep Learning](https://towardsdatascience.com/category/artificial-intelligence/deep-learning/)\nAnd Why the Hard Part of AI has Nothing to Do with AI\n[Chenggang Wu](https://towardsdatascience.com/author/cgwu0530/)\nJuly 23, 20204 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2021/02/04ebhlA-s4lx0MG67-scaled.jpg)](https://towardsdatascience.com/what-i-learned-setting-up-storage-for-a-machine-learning-project-7ae1e5668762/)\n## [What I Learned Setting up Storage for a Machine Learning Project](https://towardsdatascience.com/what-i-learned-setting-up-storage-for-a-machine-learning-project-7ae1e5668762/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nA journey towards a fast and scalable storage solution\n[Stefan Pfaffel](https://towardsdatascience.com/author/stfsy/)\nFebruary 8, 20216 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2020/11/00I68wa2MMqPyl22H-scaled.jpg)](https://towardsdatascience.com/gpu-optional-python-be36a02b634d/)\n## [GPU-Optional Python](https://towardsdatascience.com/gpu-optional-python-be36a02b634d/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nWrite code that exploits a GPU when available and desirable, but that runs fine on your CPUs when not\n[Carl Kadie](https://towardsdatascience.com/author/carlmkadie/)\nNovember 11, 20206 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2021/07/1wNmpuTSlUNaqlAG4pSfqxg.png)](https://towardsdatascience.com/regional-and-online-learnable-fields-cbb18b74e01e/)\n## [Regional and Online Learnable Fields](https://towardsdatascience.com/regional-and-online-learnable-fields-cbb18b74e01e/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nA new, not so new, clustering algorithm\n[Johannes Wohlenberg](https://towardsdatascience.com/author/johanneswohlenberg/)\nJuly 13, 20216 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2022/02/0qLP9Ef9DilngawXw-scaled.jpg)](https://towardsdatascience.com/explainable-ai-xai-with-class-maps-d0e137a91d2c/)\n## [Explainable AI (XAI) with Class Maps](https://towardsdatascience.com/explainable-ai-xai-with-class-maps-d0e137a91d2c/)\n[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)\nIntroducing a novel visual tool for explaining the results of classification algorithms, with examples in R and Python.\n[Nura Kawa](https://towardsdatascience.com/author/nurakawa/)\nFebruary 2, 202213 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2022/02/1QWb2KdgNbXafiHs7ZLbbrg.png)](https://towardsdatascience.com/discovering-powerful-data-the-guardian-news-api-into-python-for-nlp-1829b568fb0f/)\n## [Discovering Powerful Data: An NLP Goldmine](https://towardsdatascience.com/discovering-powerful-data-the-guardian-news-api-into-python-for-nlp-1829b568fb0f/)\n[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)\nUsing the Free Guardian News API with Python to Unlock a Plethora of Tagged Data\n[Adrian Causby](https://towardsdatascience.com/author/adrian.causby15/)\nFebruary 17, 20221 min read\n  * [![](https://assets.insightmediagroup.io/media/wp-content/uploads/2022/05/1li-6kJ_3Q_MuwnHERZTcwA.png)](https://towardsdatascience.com/hierarchical-clustering-and-k-means-clustering-on-country-data-84b2bf54d282/)\n## [Hierarchical Clustering and K-means Clustering on Country Data](https://towardsdatascience.com/hierarchical-clustering-and-k-means-clustering-on-country-data-84b2bf54d282/)\n[Data Science](https://towardsdatascience.com/category/data-science/)\nAn overview of two clustering techniques and comparing the results\n[Andrea Gustafsen](https://towardsdatascience.com/author/andreagustafsen/)\nMay 4, 202214 min read\n\n\n  * [YouTube](https://www.youtube.com/c/TowardsDataScience)\n  * [X](https://x.com/TDataScience)\n  * [LinkedIn](https://www.linkedin.com/company/towards-data-science/)\n  * [Threads](https://www.threads.net/@towardsdatascience)\n  * [Bluesky](https://bsky.app/profile/towardsdatascience.com)\n\n\n[](https://towardsdatascience.com/)\nYour home for data science and AI. The world's leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.\nSubscribe to Our Newsletter[Write For TDS](https://towardsdatascience.com/submissions/)•[About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)•[Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)•[Privacy Policy](https://towardsdatascience.com/privacy-policy/)•[Terms of Use](https://towardsdatascience.com/website-terms-of-use/)\n© Insight Media Group, LLC 2026•[The New Stack](https://thenewstack.io)•[roadmap.sh](https://roadmap.sh)•[Creator Network](https://insightmediagroup.io/creator-network)\n####  Recommended Articles \nClose\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607686/image?etag=1782161464448&width=500&height=500) How Relevance Models Foreshadowed Transformers for NLP ](https://towardsdatascience.com/how-relevance-models-foreshadowed-transformers-for-nlp/ \"How Relevance Models Foreshadowed Transformers for NLP\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607639/image?etag=1781250125848&width=500&height=500) Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1 ](https://towardsdatascience.com/javascript-fatigue-you-dont-need-js-to-build-chatgpt/ \"Javascript Fatigue: HTMX Is All You Need to Build ChatGPT — Part 1\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/607930/image?etag=1782124566054&width=500&height=500) The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs ](https://towardsdatascience.com/subset-sum-problem-solved-in-linear-time-for-dense-enough-inputs/ \"The Subset Sum Problem Solved in Linear Time for Dense Enough Inputs\")\n  * [ ![](https://h030.towardsdatascience.com/rest/v2/contentStores/d7d3a4ab-7caf-4383-bbd2-fbf7fb4f1787/items/3660/image?etag=1780176246070&width=500&height=500) Does Your Company Have a Data Strategy? ](https://towardsdatascience.com/does-your-company-have-a-data-strategy-e8944dbec4ae/ \"Does Your Company Have a Data Strategy?\")\n\n\n",
  "body_base_path": "attachments/f7c049d9569d/pages/",
  "body_markdown_path": "attachments/f7c049d9569d/pages/archived-body.md",
  "local_html": "attachments/f7c049d9569d/pages/archived-body.html",
  "body_source": "prior_capture"
}