skip to main content
ANIMESH BULUSU | అనిమేష్ బులుసు

Run websites locally on Windows with Caddy

Every idea of mine starts its implementation either as a spreadsheet, database or a static site. I am a big fan of static sites as they are often a gateway to developing the full-fledged web app.

To run them locally, only a lightweight webserver is needed instead of webpack et al. For the longest time I used a tiny go app called devd on Windows. It is simple to use. Install it and run the command devd in any directory with static files (HTML/CSS and may be some JS). That's it, the website will be available on the specified port instantly.

To run one of my static websites, I used to run this command daily on terminal:

$ cd source\repos\_sourcehut\anmsh.net && devd -lp 8091 .

I needed to automate this process as I had more local websites coming up. I needed something quick and easy.

First I put the above command in a batch file and then tried to run it as a service using the sc command. For some reason, it did not work out. Batch files simply don't translate into Windows services as I learned.

Then, I recollected the legendary instsrv and srvany executables which were made for converting anything into a service. I did not go this route as I was not sure and did not remember whether it would work on batch files.

At this point, I found other tools like nssm etc, but it felt like an opportunity to use a proper webserver. This resulted in some webserver shopping.

I briefly recounted my IIS days (hello freb logs). I love nginx on Linux, but it seemed to be some adventure on Windows the last time I thought of this combination. Hence Caddy was an ultimate choice.

Caddy is a fast webserver written in go with a great feature list. Install it with scoop:

scoop install caddy

Navigate to the directory where a static website exists. Serve this static website from within the directory using this command:

$ caddy file-server --listen :5000

Resulting output indicates that the directory is now served on port 5000.

...
...
{"level":"info","ts":1654843359.6882875,"msg":"Caddy serving static files on :5000"}
...

The next step is to setup Caddy as a background service so that my websites are running all the time. For this, there are a few ways to do this: 1. Winsw, and 2. the old method of using instsrv.exe and srvany.exe.

Winsw is a wrapper executable that can be used to host any executable as a windows service. Winsw is also the recommended way from Caddy documentation. Install it with scoop.

$ scoop install winsw

Download: (OK):download completed.
Checking hash of WinSW-x64.exe ... ok.
Linking ~\scoop\apps\winsw\current => ~\scoop\apps\winsw\2.11.0
Creating shim for 'WinSW'.
'winsw' (2.11.0) was installed successfully!

Find the winsw executable to do the next step.

$ scoop prefix winsw
C:\Users\anmsh\scoop\apps\winsw\current

Open directory

WinSW install directory

Copy WinSW.exe into another directory and rename it as caddy-service.exe.

Caddy directory

Create a caddy-service.xml with following contents:

<service>
  <id>caddy</id>
  <name>Caddy Web Server (powered by WinSW)</name>
  <description>Caddy Web Server (https://caddyserver.com/)</description>
  <executable>C:\Users\anmsh\scoop\apps\caddy\current\caddy.exe</executable>
  <arguments>run</arguments>
  <log mode="roll-by-time">
    <pattern>yyyy-MM-dd</pattern>
  </log>
</service>

In my case <executable> points to the scoop apps installation path. If caddy was installed in an other way, then use that path to caddy.exe instead.

Now install caddy as a service using:

$ caddy-service install

Open services.msc to verify whether caddy got installed. Another way to do this is with the sc command:

$ sc query caddy

SERVICE_NAME: caddy
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 1  STOPPED
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

Caddy is installed as a service but is in the STOPPED state at the moment. A log file caddy-service.wrapper.log is created in the same directory.

2022-06-10 17:54:01,447 DEBUG - Starting WinSW in console mode
2022-06-10 17:54:04,675 DEBUG - Starting WinSW in console mode
2022-06-10 17:54:04,701 INFO  - Installing service 'Caddy Web Server (powered by WinSW) (caddy)'...
2022-06-10 17:54:04,730 INFO  - Service 'Caddy Web Server (powered by WinSW) (caddy)' was installed successfully.
2022-06-10 18:02:34,960 DEBUG - Starting WinSW in service mode
2022-06-10 18:02:35,051 INFO  - Starting C:\Users\anmsh\scoop\apps\caddy\current\caddy.exe run
...

Caddy is ready. Now defining local websites is all that is left to do. Create an extensionless file Caddyfile in the same directory where caddy-service.exe exists.

$ touch Caddyfile

Specify websites with site addresses:

http://localhost:5000 {
    root C:\Users\anmsh\source\repos\_gitlab\anmsh.gitlab.io\out
    file_server
}

http://localhost:6001 {
    root C:\Users\anmsh\source\repos\_sourcehut\anmsh.net
    file_server
}

Here, two local websites are specified with 5000 and 6001 ports respectively. The file_serverdirective tells caddy to display static files in the root. Omit the http:// part of the site addresses to use a HTTPS URL.

Let caddy know that it has a configuration file to utilize.

$ caddy reload

{"level":"info","ts":1654883253.7133667,"msg":"using adjacent Caddyfile"}

The Caddyfile next to the caddy-service.exe is automatically picked to run. The two sites can now be reached in the browser on their respective ports.

Two log files caddy-service_2022-06-10.out.log and caddy-service_2022-06-10.err.log are created upon the reload. If something isn't working, check this err.log file.

Restart the machine to make sure these websites are running on Windows startup.

After restarting the machine, check the caddy service status. It should be in the RUNNING state.

$ sc query caddy

SERVICE_NAME: caddy
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

References:

https://caddyserver.com/docs/running#windows-service