Skip to content
Tutorials

Deploy a .NET Blog with Docker and Caddy (HTTPS in Minutes)

A step-by-step walkthrough for deploying a self-hosted .NET blog with Docker, Caddy for automatic HTTPS, environment variables, and SQL Server.

Once your content is ready, deployment should be the boring part. With Docker for packaging and Caddy for the reverse proxy, you can put a self-hosted .NET blog online with valid HTTPS in a matter of minutes, and Caddy will renew the certificate for you forever after. This walkthrough assumes a Linux server with Docker installed; if you are still setting that up, start with our guide to self-hosting a blog on a Linux VPS and then come back here.

Why Docker plus Caddy

Docker gives you a reproducible build: the same image runs on your laptop and your server, with no "works on my machine" surprises. Caddy is a small reverse proxy whose defining feature is automatic HTTPS. Point it at a domain, and it obtains and renews a Let's Encrypt certificate on its own. Together they remove the two fiddliest parts of shipping a .NET app: the runtime environment and the TLS handshake.

The Dockerfile

Inkwell ships with a Dockerfile, so most readers can skip ahead, but it is worth understanding the multi-stage build. The first stage compiles and publishes; the second copies only the published output into a slim runtime image.

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
EXPOSE 8080
ENTRYPOINT ["dotnet", "Inkwell.dll"]

Build it once and confirm it runs locally before involving a proxy or a database.

docker build -t inkwell .
docker run --rm -p 8080:8080 inkwell

SQL Server and environment variables

Inkwell uses SQL Server in production. Rather than baking secrets into the image, pass them as environment variables at runtime. A Compose file keeps the blog and the database side by side and lets them talk over a private network.

services:
  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      ACCEPT_EULA: "Y"
      MSSQL_SA_PASSWORD: "Your_strong_Pass123"
    volumes:
      - mssqldata:/var/opt/mssql
  web:
    image: inkwell
    environment:
      ConnectionStrings__Default: "Server=db;Database=inkwell;User Id=sa;Password=Your_strong_Pass123;TrustServerCertificate=True"
    depends_on:
      - db
volumes:
  mssqldata:

Note the double underscore in ConnectionStrings__Default: that is how .NET maps an environment variable onto a nested configuration key. Keep real passwords in a .env file or your host's secret store, never in version control.

Caddy for automatic HTTPS

Now put Caddy in front. The Caddyfile is famously short: a domain, the word reverse_proxy, and the address of your container. Caddy handles the certificate the first time the domain resolves to your server.

blog.example.com {
    reverse_proxy web:8080
}

Add Caddy as a third service in the same Compose file, expose ports 80 and 443, and mount a volume so certificates survive restarts. On docker compose up -d, Caddy negotiates TLS and your blog answers on https://blog.example.com with a green padlock and no manual certificate steps.

Caddy turns HTTPS from a weekend chore into a single line of config that renews itself.

Going live and staying live

  • Point your domain's A record at the server before the first compose up so Caddy can complete the certificate challenge.
  • Open only ports 80 and 443 to the public; keep the app port internal to the Docker network.
  • Back up the SQL Server volume on a schedule; the database is the only stateful piece.
  • Pull a new image and run docker compose up -d to upgrade with near-zero downtime.

For the exact commands and configuration shipped with the current release, see the install documentation. From there, deployment really is the boring part, which is exactly how it should be.

Frequently asked questions

Why use Caddy instead of Nginx for a .NET blog?

Caddy provisions and renews Let's Encrypt certificates automatically with a one-line config, while Nginx requires you to wire up Certbot and renewal hooks yourself. For a self-hosted blog, Caddy removes the most error-prone part of going live.

How do I pass database credentials to a .NET app in Docker?

Set them as environment variables at runtime, using double underscores to map nested keys, for example ConnectionStrings__Default. Keep the real values in a .env file or a secret store rather than baking them into the image or committing them.

Which database does Inkwell use in production?

Inkwell uses SQL Server, running SQL Server 2019 or newer in production, with SQL Server LocalDB available for development. SQL Server pairs cleanly with a Docker Compose setup and the provided Caddy plus SQL Server template.

Does Inkwell include a Dockerfile?

Yes. Inkwell ships a Dockerfile and a Caddy plus SQL Server Compose template, so you can build the image and stand up the full stack without writing the container configuration from scratch.

Ready to host your own blog?

Inkwell is free, open-source, and self-hosted — your content, your server, your rules. Deploy in minutes on .NET 10.

Read the install guide ← Back to the blog