Sign inSign up

malasaur/mxproxy

By malasaur

•Updated about 1 month ago

A simple, powerful, and featureful Docker-aware reverse proxy powered by Caddy

Image
Networking
Developer tools
Web servers
0

129

malasaur/mxproxy repository overview

⁠MXProxy

MXProxy is a Docker-aware proxy that turns Docker labels into Caddy configuration.

Instead of writing Caddyfiles by hand, you define your proxy configuration directly in Docker Compose files using a small, human-friendly DSL.

MXProxy watches Docker events, generates Caddy configuration automatically, and reloads Caddy when needed. No need to manually restart Caddy.

It also integrates with a few other services, such as Anubis⁠.

⁠Installation

MXProxy runs as a Docker container and communicates with the Docker daemon through its socket.

Create a compose.yaml:

services:
  mxproxy:
    image: malasaur/mxproxy:latest
    container_name: mxproxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443/tcp"
      - "443:443/udp"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/data
      - ./config:/config
    networks: [mxproxy]

networks:
  mxproxy:
    external: true

Currently, MXProxy requires an external network in order to work. By default, it uses mxproxy. All services that MXProxy should proxy must be connected to this network.

Create the network using:

docker network create mxproxy --ipv6

Then start MXProxy:

docker compose up -d

⁠Usage

Once MXProxy is running, you can define your configuration in the labels section of any service in Docker Compose files.

For example:

services:
  app:
    image: traefik/whoami
    networks: [mxproxy]
    labels:
      # MXProxy configuration goes here

networks:
  mxproxy:
    external: true
⁠Reverse Proxy

The current most powerful MXProxy directive is:

labels:
  reverse_proxy: example.com -> 8080

This is equivalent to the following Caddyfile:

example.com {
  reverse_proxy <container-ip>:8080
}

MXProxy automatically detects and adds the container's IP.

You can also specify multiple proxies in one label:

labels:
  reverse_proxy: |
    example.com -> 8080
    api.example.com -> 8081

This is equivalent to the following Caddyfile:

example.com {
  reverse_proxy <container-ip>:8080
}

api.example.com {
  reverse_proxy <container-ip>:8081
}
⁠More Upstreams

The destination does not have to be a Docker port. It can be:

  • A Docker container:

    reverse_proxy: example.com -> other-app:8000
    
  • A custom IP/port:

    reverse_proxy: example.com -> 192.168.1.100:8080
    
  • A remote URL:

    reverse_proxy: example.com -> https://example.org
    
⁠Paths

MXProxy supports paths on both sides of a proxy.

A few examples:

⁠Strip a path
labels:
  reverse_proxy: example.com/api -> 8080

Requests are translated like this:

/api     -> /
/api/foo -> /foo
⁠Append a path
labels:
  reverse_proxy: example.com -> 8080/backend

Results in:

/    -> /backend
/foo -> /backend/foo
⁠Rewrite both sides
labels:
  reverse_proxy: example.com/api -> 8080/backend

Results in:

/api     -> /backend
/api/foo -> /backend/foo
⁠Multiple proxies
labels:
  reverse_proxy: |
    example.com -> 8080
    example.com/api -> 8081
/        -> 8080/
/home    -> 8080/home
/api     -> 8081/
/api/foo -> 8081/foo

This setup allows, for example, to proxy different services running on different ports under the same domain.

⁠Middleware

The reverse_proxy directive supports middleware. They are custom services that MXProxy automatically sets up and integrates with to provide additional functionality.

The syntax is:

labels:
  reverse_proxy: source -> middleware -> destination

Currently, the only supported middleware is Anubis⁠. More integrations will be added in the future.

⁠Anubis

You can write a reverse_proxy like this:

labels:
  reverse_proxy: example.com -> anubis -> 8080

MXProxy automatically pulls and deploys a new Anubis container, then configures it to proxy requests to 8080.

When the container with the above configuration is removed, MXProxy automatically stops and removes the Anubis container too.

This way, you can protect your services with Anubis without having to configure it or deploy it manually.

⁠Headers

You can control the headers sent and received by the upstream service through the headers subdirective.

⁠Set a header

This:

labels:
  reverse_proxy: example.com -> 8080
  reverse_proxy.headers: "> X-MXProxy-Test hello"

sets the X-MXProxy-Test: hello header to the upstream request. This means the service running on port 8080 will see this header in each request.

labels:
  reverse_proxy: example.com -> 8080
  reverse_proxy.headers: "< X-MXProxy-Test hello"

sets the downstream header, meaning it will appear in the response and be visible by the client.

labels:
  reverse_proxy: example.com -> 8080
  reverse_proxy.headers: "<> X-MXProxy-Test hello"

sets the header in both directions.

⁠Add a header

Prefix the header name with +:

labels:
  reverse_proxy: example.com -> 8080
  reverse_proxy.headers: "> +X-MXProxy-Test"

This passes the incoming request header through the upstream.

For example:

curl -H "X-MXProxy-Test: hello" https://example.com

causes the upstream to receive X-MXProxy-Test: hello.

⁠Delete a header

Prefix the header name with -:

labels:
  reverse_proxy: example.com -> 8080
  reverse_proxy.headers: "> -X-MXProxy-Test"

or from response headers:

labels:
  reverse_proxy: example.com -> 8080
  reverse_proxy.headers: "< -X-MXProxy-Test"

or both directions:

labels:
  reverse_proxy: example.com -> 8080
  reverse_proxy.headers: "<> -X-MXProxy-Test"
⁠Multiple header rules

You can specify multiple rules as follows:

labels:
  reverse_proxy: example.com -> 8080
  reverse_proxy.headers: |
    > X-Request-Source mxproxy
    > +X-Forwarder-For
    < X-Served-By mxproxy
    <> X-Test enabled
    < -Server
⁠Groups

MXProxy supports grouping multiple directives and applying subdirectives to them.

For example:

labels:
  reverse_proxy/site: |
    example.com -> 8080
    api.example.com -> 8081

  reverse_proxy/site.headers: |
    > X-MXProxy-Group site

The header rule is applied to both reverse proxies in the site group.

⁠Basic auth

MXProxy supports HTTP Basic Authentication.

The auth directive can contain one or more username/password hashes:

labels:
  reverse_proxy: example.com -> 8080
  auth: admin:$$2b$$...

Note that you may need to replace $ with $$ in compose files, as that would cause Docker to treat it as an environment variable.

Basic auth can be combined with other directives:

labels:
  reverse_proxy: example.com -> 8080
  auth: admin:$$2b$$...
⁠Custom responses

Use respond to return a response without contacting an upstream.

⁠Status only
labels:
  respond: example.com -> 204
⁠Body only
labels:
  respond: example.com -> "Hello from MXProxy!"
⁠Status and body
labels:
  respond: example.com -> 418 "I'm a teapot"
⁠Compression

Compression can be enabled with:

labels:
  compress: gzip
⁠Cloudflare

MXProxy integrates with Cloudflare to automatically configure ACME challenges and set up DDNS.

To enable this integration, use the cloudflare directive with your Cloudflare API token:

labels:
  cloudflare: $${API_KEY}

Warning! The above snippet uses $${API_KEY} rather than ${API_KEY}.

If you used ${API_KEY} directly, Docker would immediately replace it with the value of the API_KEY environment variable. This means MXProxy would read the variable directly, and paste is as-is in the generated Caddy configuration.

That can be a security risk, as it would result in having another file on your system that has the value of your Cloudflare API token written in plaintext, potentially without your knowledge of it.

By using $${API_KEY}, Docker will not replace the variable and literally pass "${API_KEY}" as the value. MXProxy will detect this and replace it with {env.API_KEY}, which will cause Caddy to actually load the value from the environment variable, assuming that the .env file where API_KEY is set is included in the container with env_file.

services:
  my-app:
    image: ...
    env_file:
      - .env
    labels:
      cloudflare: $${API_KEY} # gets replaced with `{env.API_KEY}`

You may also use {env.API_KEY} directly. The result is the same.

By simply setting the API key, MXProxy will automatically use Cloudflare as a provider for ACME challenges.

After setting an API key, you can also set up a DDNS for Cloudflare by using the cloudflare.ddns subdirective:

labels:
  cloudflare: $${API_KEY}
  cloudflare.ddns: example.com

This will automatically set up and update an A record for example.com pointing to the IP address of your server.

You can specify multiple domains and subdomains using the same syntax described by the caddy-dynamicdns⁠ module:

labels:
  cloudflare: $${API_KEY}
  # example.com, www.example.com, subdomain.example.net
  cloudflare.ddns: |
    example.com @ www
    example.net subdomain

Tag summary

Content type

Image

Digest

sha256:044059d56…

Size

89.9 MB

Last updated

about 1 month ago

docker pull malasaur/mxproxy