Caddy sidecar for Angular SPA in Azure - 404 routes

1. Caddy version (caddy version):

2.3.0

2. How I run Caddy:

I have an Angular SPA deployed to Azure storage account and an ExpressJS API instance deployed to Azure container instance. I use Caddy as a sidecar to automatically get the TLS certificates and as a reverse proxy so that both the Angular SPA and ExpressJS API instance appear to run in the same domain (for security purposes and to avoid CORS).

a. System environment:

Docker

b. Command:

Default command in caddy image.

c. Service/unit/compose file:

  - name: caddy
    properties:
      image: caddy:2.3.0
      ports:
        - protocol: tcp
          port: 443
        - protocol: tcp
          port: 80
      volumeMounts:
        - name: caddy-data
          mountPath: /data
        - name: caddy-config
          mountPath: /config
        - name: caddyfile
          mountPath: /etc/caddy
        - name: index
          mountPath: /usr/share/caddy
      resources:
        requests:
          cpu: 1.0
          memoryInGB: 1.0
  volumes:
    - name: caddy-data
      emptyDir: {}
    - name: caddy-config
      emptyDir: {}
    - name: caddyfile
      secret:
        Caddyfile: d3d3Lm1hcmtldGVyc2NvbXBhc3MuY29tIHsKICAgIHJldmVyc2VfcHJveHkgLyogaHR0cHM6Ly9tYXJrZXRlcnNjb21wYXNzLmF6dXJlZWRnZS5uZXQKICAgIHJldmVyc2VfcHJveHkgL2FwaSogbG9jYWxob3N0OjMwMDAKCiAgICB0cnlfZmlsZXMge3BhdGh9IHtwYXRofS8gL2luZGV4Lmh0bWwKICAgIGZpbGVfc2VydmVyCn0KCm1jbXZwLmNlbnRyYWx1cy5henVyZWNvbnRhaW5lci5pbyB7CiAgICByZXZlcnNlX3Byb3h5IC8qIGh0dHBzOi8vbWFya2V0ZXJzY29tcGFzcy5henVyZWVkZ2UubmV0CiAgICByZXZlcnNlX3Byb3h5IC9hcGkqIGxvY2FsaG9zdDozMDAwCn0KCg==
    - name: index
      secret:
        index.html: PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMCBUcmFuc2l0aW9uYWwvL0VOIj4KPGh0bWw+CiAgPGhlYWQ+CiAgICA8dGl0bGU+PC90aXRsZT4KICA8L2hlYWQ+CiAgPGJvZHk+PC9ib2R5Pgo8L2h0bWw+Cg==

d. My complete Caddyfile or JSON config:

www.marketerscompass.com {
    reverse_proxy /* https://marketerscompass.azureedge.net
    reverse_proxy /api* localhost:3000

    try_files {path} {path}/ /index.html
    file_server
}

mcmvp.centralus.azurecontainer.io {
    reverse_proxy /* https://marketerscompass.azureedge.net
    reverse_proxy /api* localhost:3000
}

3. The problem I’m having:

As is typical with SPAs, the initial client-side route request results in a 404 in the browser. I’d prefer to eliminate this 404. To do this, I’ve mounted an empty index.html to the caddy container and attempted to use try_files to load it. However, I think the fact that the reverse_proxy also matches it is causing issues. I’m not really sure, to be honest.

4. Error messages and/or full log output:

N/A

5. What I already tried:

I’ve scoured this forum and stackoverflow for tips, but my use case seems to be unique in that I’m not actually serving the Angular SPA from Caddy, only reverse proxying to it.

6. Links to relevant resources:

Thanks for any assistance!

I’m sure someone who has more time at the moment can give you a detailed answer, but real quick I’ll post a link to this article which probably will lead you to your solution: Composing in the Caddyfile

1 Like

Thanks, Matt.

I read through the document and it’s clearer now, but I think I’m still missing some details.

Firstly, I don’t need try_files, so I’ve removed that. What I want is when the reverse proxy to /* misses, I want it to fall back to https://marketerscompass.azureedge.net/index.html. Is this possible?

I suppose the other option is to define a brittle matcher that includes the known files of my static SPA, but I’d like to avoid that.

www.marketerscompass.com {
    handle /* {
        reverse_proxy https://marketerscompass.azureedge.net
        // How to make it rewrite to https://marketerscompass.azureedge.net/index.html if there
        // is a reverse_proxy miss?
    }
    handle /api* {
        reverse_proxy localhost:3000
    }
}

Just to be clear, that will never “miss” – all requests have a path that starts with / so those handlers will always be invoked.

I think I finally figured it out. I’m not sure if this is the best way to do it, but I guess it will do for now. Thanks for the link, it helped!

www.marketerscompass.com {
    @static {
        path_regexp static \.(css|js|woff2|ico|svg)$
    }

    route {
        reverse_proxy /api* localhost:3000
        reverse_proxy @static https://marketerscompass.azureedge.net
        rewrite * /index.html
        reverse_proxy /* https://marketerscompass.azureedge.net
    }
}