Hosting the Application
Before you start
You need: Stage 10. You have a Release build of the API, a built frontend, and no secrets in source control.
You also need: a Microsoft account, and a card for Azure identity verification if you take that route. The free tier does not charge it, but Azure asks for it.
Time: 3–5 hours the first time. Under thirty minutes every time after.
Learning objective
Deploy the API, the frontend and the database somewhere a stranger can reach them, and explain every configuration difference between that environment and your laptop.
Topics
- Why a live URL changes the conversation
- What has to be hosted, and where
- The database first
- Deploying the API to Azure App Service
- Configuration and connection strings in the cloud
- The frontend, and the CORS change it forces
- The IIS route instead
- Keeping it cheap, and what to do when it sleeps
Why bother
Stage 10 ended with a build in a folder. That is a deployable artefact, not a deployment. This stage puts it somewhere with a URL.
The practical reason: a link on a resume is opened. A GitHub repository is not, at least not first. A recruiter with sixty applications clicks a link before they clone anything, and a working screen buys the thirty seconds that gets the repository looked at too.
The better reason: deploying is where you find out what you actually understood.
| It worked locally because | Production makes you handle it |
|---|---|
| The connection string was in a file on your machine | Configuration comes from the environment |
The API and the frontend were both localhost | Two real origins, so CORS becomes real |
| You ran as yourself against a local SQL Server | A restricted database login, over the network |
dotnet run set the environment to Development | Production, so the exception page is off |
| Certificates were the development ones | Real HTTPS |
Every one of those is an interview question, and having deployed once turns each into a story rather than a definition.
What has to be hosted
Three separate things. Students routinely try to deploy them as one and get stuck.
| Part | What it is | Where it goes |
|---|---|---|
| Database | SQL Server | A hosted SQL database |
| API | An ASP.NET Core application, always running | An app host — App Service or IIS |
| Frontend | Static HTML, CSS and JavaScript after npm run build | Any static host |
Deploy in that order. The API needs the database to start cleanly; the frontend needs the API's URL baked into its build.
The database first
Create a hosted SQL database, then run your Track 06 script against it from SSMS — the same script that drops and rebuilds locally.
Two things to do at creation time, and both catch people out:
- Set the firewall to allow your own IP, or SSMS cannot connect at all. On Azure SQL this is under Networking → Add your client IPv4 address.
- Allow Azure services to reach it, or your App Service cannot connect even though SSMS can. Same page, a separate checkbox.
Create a login for the application that is not the admin login. The application needs SELECT, INSERT, UPDATE, DELETE and EXECUTE on your procedures — nothing else. It does not need to create tables at runtime.
Seed only demonstration data. Ravi Kumar, Priya Sharma, Dr. Mehta, NexCoding Academy. Never real names, never real phone numbers. This URL is public and you are about to put it on a resume.
Free tiers change. Azure's free database offer, its size limits and its duration are not what they were two years ago and will not be this in another two. Check the current terms on the pricing page before you create anything, and set a spending limit or a budget alert on the subscription while you are there.
Deploying the API
From Visual Studio:
- Right-click the API project → Publish.
- Target Azure → Azure App Service (Windows) → Next.
- Sign in, then Create new. Pick a resource group, a region near you, and a hosting plan on the Free F1 tier.
- Finish, then Publish.
The first publish takes a few minutes and ends with a browser tab at https://your-app-name.azurewebsites.net.
It will not work yet. It has no connection string and it does not know it is in production. That is the next section, and it is the part worth understanding.
A note on the free tier. F1 gives you a small daily CPU allowance and no custom domain or scaling. That is fine for a portfolio. It also puts the app to sleep after about twenty minutes idle, so the first request after a quiet spell takes fifteen to thirty seconds while it starts. Plan for that — see the last section.
Configuration in the cloud
Your appsettings.json has no production connection string and no JWT signing key, because Stage 10 established that committing either is unrecoverable. So the host has to supply them.
In the portal, open the App Service → Configuration:
| Add | As | Value |
|---|---|---|
ConnectionStrings:SchoolDb | Connection string, type SQLAzure | The one from the database's connection strings page, with the application login |
Jwt__Key | Application setting | A newly generated key — not the one from your machine |
Jwt__Issuer, Jwt__Audience | Application settings | Your App Service URL |
ASPNETCORE_ENVIRONMENT | Application setting | Production |
The double underscore is not a typo. Environment variables cannot contain a colon, so ASP.NET Core reads Jwt__Key as the Jwt:Key you wrote in Article 03 of the ASP.NET Core track. Jwt:Key as a variable name silently does nothing, and the error you get is a confusing one about the key being missing.
Generate a new signing key for production. If the production key equals your development key, every token you minted while debugging is valid against the live site. Rotating it is one setting; explaining why you rotated it is an interview answer.
Then confirm the environment is right. Visit an endpoint that throws. You should see a plain 500 with a trace id, not a stack trace. If you see the developer exception page, ASPNETCORE_ENVIRONMENT did not take — save the setting and restart the app.
Check /version from Stage 10. It tells you which commit is live, which is the first question when something behaves oddly.
The frontend, and the CORS change
Build the frontend against the deployed API, not localhost:
npm ci
npm run build
The API base URL must come from the frontend's environment file, not be typed into a service class — the same rule as the API's connection string, for the same reason. Then upload the dist output to any static host: Azure Static Web Apps, Netlify, Vercel or GitHub Pages all take a folder.
Now the API rejects it, and the browser console says the request was blocked by CORS policy. This is correct behaviour, not a bug.
In Article 11 of the ASP.NET Core track you allowed http://localhost:5173. Your frontend is now at a real domain, and the API has never heard of it. Move the allowed origins into configuration and add the real one as an App Service setting.
Do not "fix" this with AllowAnyOrigin. It works, and it means any site on the internet can call your API with a user's credentials attached. A reviewer who sees AllowAnyOrigin next to AllowCredentials in a portfolio project has learned something about the candidate — and ASP.NET Core actually refuses that combination at startup, which is the framework telling you the same thing.
Fix CORS on the server, always. Browser extensions that disable it change nothing for your users.
The IIS route instead
If the training environment gives you a Windows server rather than a cloud subscription, the shape is the same and the steps differ.
- Install the ASP.NET Core Hosting Bundle on the server. This is the step everyone misses, and the symptom is a
500.19or500.31on every request. - In IIS Manager, add a website pointing at your published folder.
- Set the application pool's .NET CLR version to No Managed Code — ASP.NET Core runs its own runtime; IIS is only the front door.
- Give the app pool identity read access to the folder, and the database login rights to the database.
- Set
ASPNETCORE_ENVIRONMENT— inweb.config, or as a system environment variable.
500.30 means your application started and crashed, usually on a missing connection string. Turn on stdout logging in web.config, hit the site once, and read the log file. That single trick resolves most first-deployment failures on IIS.
Keeping it cheap and awake
- Set a budget alert on the subscription on day one. The free tier is free until something you created is not on it.
- Delete the resource group when you are finished with a demo. It removes everything inside in one action.
- Expect the first request to be slow on a free plan. Do not let an interviewer meet that cold start — open the URL two minutes before the call.
- Put the cold start in your README. "Hosted on a free tier; the first request may take up to thirty seconds while the app wakes." That reads as someone who understands their deployment, not as an excuse.
- Screenshot the working application and put the images in the README as well, so the project survives the day you take the hosting down.
Common mistakes
- Deploying the API before the database exists, then debugging a startup crash that is only a missing table
- Leaving the firewall closed to Azure services and concluding the connection string is wrong
- Using
Jwt:Keyinstead ofJwt__Keyin an environment variable - Reusing the development signing key in production
- Shipping with
ASPNETCORE_ENVIRONMENTunset, so the exception page is live - Answering CORS with
AllowAnyOriginrather than the real origin - Baking the API URL into the frontend source instead of its environment file
- Seeding real student names and phone numbers into a public demo
- Forgetting the Hosting Bundle on IIS and reading
500.19as a code problem - No budget alert
Practice
- Create the hosted database. Open the firewall to your IP and to Azure services.
- Run your Track 06 script against it from SSMS. Seed demonstration data only.
- Create an application login with only the rights the application needs.
- Publish the API to a Free F1 App Service.
- Add the connection string, a new JWT key, the issuer, the audience, and
ASPNETCORE_ENVIRONMENT. - Hit an endpoint that throws. Confirm you get a plain 500 and not a stack trace.
- Build the frontend against the deployed API URL and host the output.
- Watch the CORS failure happen. Fix it on the server, with the exact origin.
- Sign in and run the Stage 10 smoke test against the live site.
- Add the live URL, the cold-start note and screenshots to the README.
- Set a budget alert.
You can now
- Name the three separately hosted parts and the order they deploy in
- Create a hosted database with a firewall and a least-privilege application login
- Publish an ASP.NET Core API to App Service, or to IIS with the Hosting Bundle
- Supply connection strings and keys from the environment, with
__for: - Prove the application is running as Production
- Diagnose and correctly fix a CORS failure between two real origins
- Explain why production gets a different signing key
- Put a live URL on a resume and describe its limits honestly
Review questions
- Why must the database be deployed before the API, and the API before the frontend?
- Why does
Jwt__Keywork as an environment variable whenJwt:Keydoes not? - Your frontend worked locally and is blocked in production. What changed, and where is it fixed?
- What does a
500.30on IIS tell you that a500.19does not? - Why is reusing your development signing key in production a problem, given that nobody else knows it?
Next: return to the Track 01 overview, or continue to Track 11 — Angular or Track 12 — React.