Skip to content

Commit 5df93de

Browse files
authored
Merge pull request #240 from nanotaboada/feature/docker-compose
feat(container): add Docker Compose support with persistent SQLite volume
2 parents 985fb5b + d6e7995 commit 5df93de

File tree

10 files changed

+278
-644
lines changed

10 files changed

+278
-644
lines changed

.gitignore

Lines changed: 3 additions & 479 deletions
Large diffs are not rendered by default.

Dockerfile

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,73 @@
1-
# - Stage 1 --------------------------------------------------------------------
1+
# ------------------------------------------------------------------------------
2+
# Stage 1: Builder
3+
# This stage builds the application and its dependencies.
4+
# ------------------------------------------------------------------------------
5+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder
26

3-
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
4-
WORKDIR /src
7+
WORKDIR /src
58

6-
# Copy and restore dependencies
7-
COPY src/Dotnet.Samples.AspNetCore.WebApi/*.csproj ./Dotnet.Samples.AspNetCore.WebApi/
8-
RUN dotnet restore ./Dotnet.Samples.AspNetCore.WebApi
9+
# Restore dependencies
10+
COPY src/Dotnet.Samples.AspNetCore.WebApi/*.csproj ./Dotnet.Samples.AspNetCore.WebApi/
11+
RUN dotnet restore ./Dotnet.Samples.AspNetCore.WebApi
912

10-
# Copy source and publish
11-
COPY src/Dotnet.Samples.AspNetCore.WebApi ./Dotnet.Samples.AspNetCore.WebApi
12-
WORKDIR /src/Dotnet.Samples.AspNetCore.WebApi
13-
RUN dotnet publish -c Release -o /app/publish
13+
# Copy source code and pre-seeded SQLite database
14+
COPY src/Dotnet.Samples.AspNetCore.WebApi ./Dotnet.Samples.AspNetCore.WebApi
1415

15-
# - Stage 2 --------------------------------------------------------------------
16+
WORKDIR /src/Dotnet.Samples.AspNetCore.WebApi
1617

17-
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
18-
WORKDIR /app
18+
# Build solution and publish release
19+
RUN dotnet publish -c Release -o /app/publish
1920

20-
# Copy published output
21-
# Note: This includes the SQLite database because it's marked as <Content> with
22-
# <CopyToOutputDirectory> in the .csproj file. No need to copy it manually.
23-
COPY --from=build /app/publish .
21+
# ------------------------------------------------------------------------------
22+
# Stage 2: Runtime
23+
# This stage creates the final, minimal image to run the application.
24+
# ------------------------------------------------------------------------------
25+
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
2426

25-
# Add non-root user (aspnetcore) for security hardening
26-
RUN adduser --disabled-password --gecos '' aspnetcore \
27-
&& chown -R aspnetcore:aspnetcore /app
28-
USER aspnetcore
27+
WORKDIR /app
2928

30-
# Set environment variables
31-
ENV ASPNETCORE_URLS=http://+:9000
32-
ENV ASPNETCORE_ENVIRONMENT=Production
29+
# Install curl for health check
30+
RUN apt-get update && apt-get install -y --no-install-recommends curl && \
31+
rm -rf /var/lib/apt/lists/*
3332

34-
# Default entrypoint
35-
ENTRYPOINT ["dotnet", "Dotnet.Samples.AspNetCore.WebApi.dll"]
33+
# Metadata labels for the image. These are useful for registries and inspection.
34+
LABEL org.opencontainers.image.title="🧪 Web API made with .NET 8 (LTS) and ASP.NET Core"
35+
LABEL org.opencontainers.image.description="Proof of Concept for a Web API made with .NET 8 (LTS) and ASP.NET Core"
36+
LABEL org.opencontainers.image.licenses="MIT"
37+
LABEL org.opencontainers.image.source="https://github.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi"
38+
39+
# Set environment variables
40+
ENV ASPNETCORE_URLS=http://+:9000
41+
ENV ASPNETCORE_ENVIRONMENT=Production
42+
43+
# Copy published app from builder
44+
COPY --from=builder /app/publish .
45+
46+
# Copy metadata docs for container registries (e.g.: GitHub Container Registry)
47+
COPY --chmod=444 README.md ./
48+
COPY --chmod=555 assets ./assets
49+
50+
# https://rules.sonarsource.com/docker/RSPEC-6504/
51+
52+
# Copy entrypoint and healthcheck scripts
53+
COPY --chmod=555 scripts/entrypoint.sh ./entrypoint.sh
54+
COPY --chmod=555 scripts/healthcheck.sh ./healthcheck.sh
55+
56+
57+
# Copy pre-seeded SQLite database as init bundle
58+
COPY --from=builder /src/Dotnet.Samples.AspNetCore.WebApi/storage/players-sqlite3.db ./docker-compose/players-sqlite3.db
59+
60+
# Create non-root user and make volume mount point writable
61+
RUN adduser --disabled-password --gecos '' aspnetcore && \
62+
mkdir -p /storage && \
63+
chown -R aspnetcore:aspnetcore /storage
64+
65+
USER aspnetcore
66+
67+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
68+
CMD ["./healthcheck.sh"]
69+
70+
EXPOSE 9000
71+
72+
ENTRYPOINT ["./entrypoint.sh"]
73+
CMD ["dotnet", "Dotnet.Samples.AspNetCore.WebApi.dll"]

README.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,39 @@ https://localhost:9000/swagger/index.html
3636

3737
## Container
3838

39-
This project includes a multi-stage `Dockerfile` for local development and production builds.
39+
### Docker Compose
4040

41-
### Build the image
41+
This setup uses [Docker Compose](https://docs.docker.com/compose/) to build and run the app and manage a persistent SQLite database stored in a Docker volume.
42+
43+
#### Build the image
44+
45+
```bash
46+
docker compose build
47+
```
48+
49+
#### Start the app
4250

4351
```bash
44-
docker build -t aspnetcore-app .
52+
docker compose up
4553
```
4654

47-
### Run the container
55+
> On first run, the container copies a pre-seeded SQLite database into a persistent volume
56+
> On subsequent runs, that volume is reused and the data is preserved
57+
58+
#### Stop the app
4859

4960
```bash
50-
docker run -p 9000:9000 aspnetcore-app
61+
docker compose down
5162
```
5263

64+
#### Optional: database reset
65+
66+
```bash
67+
docker compose down -v
68+
```
69+
70+
> This removes the volume and will reinitialize the database from the built-in seed file the next time you `up`.
71+
5372
## Credits
5473

5574
The solution has been coded using [Visual Studio Code](https://code.visualstudio.com/) with the [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) extension.

docker-compose.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
services:
2+
api:
3+
image: dotnet-samples-aspnetcore-webapi
4+
container_name: aspnetcore-app
5+
build:
6+
context: .
7+
dockerfile: Dockerfile
8+
ports:
9+
- "9000:9000"
10+
volumes:
11+
- storage:/storage/
12+
environment:
13+
- STORAGE_PATH=/storage/players-sqlite3.db
14+
restart: unless-stopped
15+
16+
volumes:
17+
storage:

scripts/entrypoint.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/sh
2+
set -e
3+
4+
IMAGE_STORAGE_PATH="/app/docker-compose/players-sqlite3.db"
5+
VOLUME_STORAGE_PATH="/storage/players-sqlite3.db"
6+
7+
echo "✔ Starting container..."
8+
9+
if [ ! -f "$VOLUME_STORAGE_PATH" ]; then
10+
echo "⚠️ No existing database file found in volume."
11+
if [ -f "$IMAGE_STORAGE_PATH" ]; then
12+
echo "🔄 Copying database file to writable volume..."
13+
cp "$IMAGE_STORAGE_PATH" "$VOLUME_STORAGE_PATH"
14+
echo "✔ Database initialized at $VOLUME_STORAGE_PATH"
15+
else
16+
echo "⚠️ Database file missing at $IMAGE_STORAGE_PATH"
17+
exit 1
18+
fi
19+
else
20+
echo "✔ Existing database file found. Skipping seed copy."
21+
fi
22+
23+
echo "✔ Ready!"
24+
echo "🚀 Launching app..."
25+
exec "$@"

scripts/healthcheck.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/sh
2+
set -e
3+
4+
# Simple health check using curl
5+
curl --fail http://localhost:9000/health

src/Dotnet.Samples.AspNetCore.WebApi/Dotnet.Samples.AspNetCore.WebApi.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</ItemGroup>
2929

3030
<ItemGroup>
31-
<Content Include="Data/players-sqlite3.db" Condition="Exists('Data/players-sqlite3.db')">
31+
<Content Include="storage/players-sqlite3.db">
3232
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3333
<PackageCopyToOutput>true</PackageCopyToOutput>
3434
</Content>

src/Dotnet.Samples.AspNetCore.WebApi/Program.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@
3636
/* Entity Framework Core ---------------------------------------------------- */
3737
builder.Services.AddDbContextPool<PlayerDbContext>(options =>
3838
{
39-
var dataSource = Path.Combine(AppContext.BaseDirectory, "Data", "players-sqlite3.db");
39+
var dataSource = Path.Combine(AppContext.BaseDirectory, "storage", "players-sqlite3.db");
40+
4041
options.UseSqlite($"Data Source={dataSource}");
42+
4143
if (builder.Environment.IsDevelopment())
4244
{
4345
options.EnableSensitiveDataLogging();
@@ -48,6 +50,7 @@
4850
builder.Services.AddScoped<IPlayerRepository, PlayerRepository>();
4951
builder.Services.AddScoped<IPlayerService, PlayerService>();
5052
builder.Services.AddMemoryCache();
53+
builder.Services.AddHealthChecks();
5154

5255
/* AutoMapper --------------------------------------------------------------- */
5356
builder.Services.AddAutoMapper(typeof(PlayerMappingProfile));
@@ -91,4 +94,7 @@
9194
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#endpoints
9295
app.MapControllers();
9396

97+
// https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks
98+
app.MapHealthChecks("/health");
99+
94100
await app.RunAsync();

0 commit comments

Comments
 (0)