Cart
Containers package your app with everything it needs and run it the same way everywhere. No fiddling with machine-specific setups, no “works on my machine” jokes.
This guide keeps things hands-on: images vs containers, writing a Dockerfile,docker run
basics, and a small docker-compose.yml
to run your API plus a database.
Build an image once, run containers many times.
Create a folder HelloApi
and drop this Program.cs
inside:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "API is up");
app.MapGet("/hello/{name}", (string name) => new { message = $"Hello, {name}" });
app.Run();
Prevent large and irrelevant files from bloating your image:
bin/
obj/
*.user
*.suo
.vscode/
.idea/
**/*.Secrets.json
Create Dockerfile
next to your .csproj
:
# 1) Build stage
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# copy csproj first to take advantage of layer caching
COPY HelloApi.csproj .
RUN dotnet restore
# copy the rest and publish
COPY . .
RUN dotnet publish -c Release -o /app /p:UseAppHost=false
# 2) Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
COPY --from=build /app ./
ENTRYPOINT ["dotnet", "HelloApi.dll"]
Why this layout?
.csproj
before source allows restore caching until dependencies change.# build the image (note the trailing dot)
docker build -t helloapi:dev .
# run a container mapping port 8080 in the container to 8080 on your machine
docker run --rm -p 8080:8080 helloapi:dev
Browse to http://localhost:8080/hello/Dotnet
and you should see JSON.
Handy flags:
--rm
removes the container when it exits.-e KEY=VALUE
sets environment variables (useful for connection strings, etc.).FROM
— base image (e.g., aspnet:9.0
, sdk:9.0
).WORKDIR
— sets the working folder for subsequent commands.COPY
— copies files into the image.RUN
— executes a command at build time (e.g., dotnet publish
).ENV
— defines environment variables.EXPOSE
— documents the port your app listens on.ENTRYPOINT
/ CMD
— what runs when the container starts.Create docker-compose.yml
at the solution root to run multiple containers together:
services:
api:
build:
context: .
dockerfile: Dockerfile
image: helloapi:dev
ports:
- "8080:8080"
environment:
ASPNETCORE_URLS: http://+:8080
ConnectionStrings__Default: Host=db;Port=5432;Database=hello;Username=postgres;Password=postgres
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: hello
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata:
Run everything:
docker compose up --build
depends_on
starts the database before the API (you should still add retry logic in your app).pgdata
volume keeps your database files between restarts..csproj
, restore, then copy source).dotnet watch
on your host, and use a bind mount to share files into the container if you need the app to run inside Docker during dev:services:
api:
volumes:
- ./:/src
command: ["dotnet", "watch", "--project", "/src/HelloApi.csproj", "run", "--urls", "http://0.0.0.0:8080"]
docker system prune -f
removes stopped containers and dangling images. Careful when using this as this will delete images and containers.Port already in use
Change the host mapping: -p 9090:8080
and browse http://localhost:9090
.
File not found at runtime
Ensure you COPY
files into the runtime stage (or from the build stage). Check paths.
Slow builds
Make sure .dockerignore
is in place and you copy the .csproj
separately to benefit from restore caching.
“Works outside Docker, fails inside”
Check environment variables. Containers are isolated; they do not see your host’s env unless you pass them in (-e
or environment:
in Compose).
# one-liners you’ll remember
docker build -t app:dev .
docker run --rm -p 8080:8080 app:dev
docker compose up --build
docker ps -a
docker logs <container>
docker exec -it <container> sh
Nick Chapsas is a .NET & C# content creator, educator and a Microsoft MVP for Developer Technologies with years of experience in Software Engineering and Engineering Management.
He has worked for some of the biggest companies in the world, building systems that served millions of users and tens of thousands of requests per second.
Nick creates free content on YouTube and is the host of the Keep Coding Podcast.
More courses by Nick Chapsas© 2025 Dometrain. All rights reserved.