Using Zrok on docker for multiple services

I've been using Zrok for a longtime, and I have tried with success the terminal app. I've tried the docker install for another service and worked, and it's cleaner as I have Docker running for those services. The problem is that I want to run more than one service in the docker install and I'm not able to figure how to set it up.

I would be able to add more services in the future

Hi @Delfi_r, welcome to the community and to zrok (and OpenZiti)!

Generally speaking there's no way to share multiple services on one install. You can run one zrok share container for each service to expose.

Did you also find this other discourse post that covers the same topic?

There's also a feature request: Single Command, Multiple Share|Access · Issue #194 · openziti/zrok · GitHub to enable this sort of functionality on the roadmap.

1 Like

I use zrok on a Mac. On this machine I run different prototype services on different ports. As you have explained I need to set up different pairs .env and compose.yml and compose up each of them so I'll end with different zrok images. I need to rename them different and take care on something in the compose.yml file?

Yes. You should be able to just run multiple containers, each container hosting a separate zrok share process, as long as you are careful with naming those containers.

Me personally, I would just use the same .env file. You would want different compose files though because you'll be sharing different shares, yes. Depending on how good you are with docker and how you're running zrok, you might be able to keep the compose file the same but alter the env file. Overall, sounds like you're on the right path though.

i can't use the same .env file as this is where the name of the share is defined and yse, I want to share different services so i'll try to use the same docker compose file and try to get different containers. I'll inform you when all is done

1 Like

Thanks for sharing your issues and progress on this! Which approach are you using to enable the zrok environment for each share container? The two main approaches are these:

  1. Docker bind volume like ~/.zrok:/.zrok and run-as user $UID
  2. Docker named volume like zrok_env:/mnt and env STATE_DIRECTORY=/mnt

The difference is that the first one uses the Docker host user's zrok environment, and the second one manages a separate zrok environment in Docker, separate from the Docker host user.

The first one is a little simpler and more immediate, whereas the second is more like a production service.

With either approach, I think you will find a way to re-use the same zrok environment for all the share containers.

Here's the docs with complete examples and tutorials for both approaches: Getting Started with Docker | Zrok

Before I try I'll want to ask another question because perhaps it's very important

I'm following Docker Compose Public Share | Zrok

I want to expose within Docker four services:

OpenWebhui running on localhost:3000 (now I use a reserved share with a terminal window)
Searxng running on localhost:4000 (now I use a reserved share with a terminal window)
Trasformer.lab on localhost:8000

Those will be on docker creating three .env files and composing three Docker containers. I'll try this, But I need first get a solution for a more complex project:

Perplexica has the front-end on 3080 (from the original 3000 all projects configure by default)

But the backend is running on localhost 3001. so I've figured I need a Caddyfile to set up a Reverse proxy:

your_zrok_share.zrok.io {
    tls off  # zrok already provides TLS/SSL, so we disable it here

    reverse_proxy / localhost:3080 {
        header_up Upgrade $http_upgrade
        header_up Connection $http_connection
    }

    reverse_proxy /api/* localhost:3001 {
        to {path}
    }

    reverse_proxy /ws/* localhost:3001 {
        to {path}
        websocket
    }
}

My doubts are: I need to reserve a zrok.io share out of this file or it will be created when I compose the Docjer file?

Where I need to put the caddy file to create a docker container for it? Wich is the right thing to do?

Great. That sounds like it will work well with zrok and Docker.

You're following the Docker public share guide which uses a separate zrok env for the container, not the zrok environment belonging to the Docker host user.

Your compose environment needs the variables mentioned in that guide you linked.

ZROK_ENABLE_TOKEN=abcd123
ZROK_BACKEND_MODE=caddy
ZROK_TARGET=/Caddyfile
ZROK_UNIQUE_NAME=your_zrok_share

In the Docker public share compose.yml, you must mount the Caddyfile in the same path specified as the target, e.g.

services:
  zrok-share:
    volumes:
      - ./Caddyfile:/Caddyfile

Your Caddyfile must follow the zrok documentation example with bind {{ .ZrokBindAddress }}.

http:// {
    bind {{ .ZrokBindAddress }}

    reverse_proxy / localhost:3080 {
        header_up Upgrade $http_upgrade
        header_up Connection $http_connection
    }

    reverse_proxy /api/* localhost:3001 {
        to {path}
    }

    reverse_proxy /ws/* localhost:3001 {
        to {path}
        websocket
    }
}

The Docker public share compose.yml has everything inside to reserve your_zrok_share and you will see the share appear in the web console when you do docker compose up. Then you can visit https://your_zrok_share.share.zrok.io

Thanks a lot for your detailed reply. A simple question: how to name the caddyfile and were to put it?

You can name it Caddyfile and put it in the same directory as compose.yml to mount it in the container like this. Ensure the file is readable by setting permissive filemode on the Docker host.

chmod -c a+r ./Caddyfile
services:
  your_zrok_share:
    image: ${ZROK_CONTAINER_IMAGE:-docker.io/openziti/zrok}
    restart: unless-stopped
    entrypoint: zrok-share.bash
    depends_on:
      zrok-enable:
        condition: service_completed_successfully
    volumes:
      - zrok_env:/mnt
      - ./Caddyfile:/Caddyfile
    environment:
      ZROK_UNIQUE_NAME:     "your_zrok_share"
      ZROK_BACKEND_MODE:    caddy
      ZROK_TARGET:          /Caddyfile
      STATE_DIRECTORY: /mnt  # zrok homedir in container

You can have many share containers like this, each mounting the zrok_env volume to provide the enabled environment.

Today I'm unable to set up a simple proxy Zrok service on docker, zrok is running on the console. After many retries, the share is visible on the Zrok web console but the service is not running to the target.

What I see now that if I put the .env and compose files on a folder called ZROK2 the container is named ZROK2 so it will be easy to set up many containers

I suspect the zrok share container couldn't reach the zrok controller, backend target, or both.

You'll have many zrok environments this way, one for each share, which may not be what you want. You can instead use a single zrok environment with many share containers by following the YAML snippet for "your_zrok_share" as an example.

Here's the concept: in your compose project, have one zrok-enable container that enables the zrok environment. Mount the same volume from zrok-enable on each share container in the same path, with STATE_DIRECTORY env var set to the mountpoint. The shares will use the same environment. You can add numerous shares in the same compose file this way, or use separate files in the same compose project.

I've been playing with this idea today as I'm also interested in multiple shares in a single environment from a single docker compose stack/swarm/instance/[insert proper term here].

What I'm showing here doesn't relate directly to the OP's question, but it's in the ballpark and might be interesting to some.

As a starting point, I used the compose file from here. Using the extends feature of docker compose, you can write a kind of zrok share template service and then use it to declare a service for each share in your compose file.

With the following two files in the same directory, try docker compose up.

Note services zrok-share-1 and zrok-share-2 and the use of extends.

compose.yaml

services:
  zrok-init:
    image: busybox
    # matches uid:gid of "ziggy" in zrok container image
    command: chown -Rc 2171:2171 /mnt/.zrok
    user: root
    volumes:
      - zrok_env:/mnt/.zrok

  # enable zrok environment
  zrok-enable:
    image: ${ZROK_CONTAINER_IMAGE:-docker.io/openziti/zrok}
    depends_on:
      zrok-init:
        condition: service_completed_successfully
    entrypoint: zrok-enable.bash
    volumes:
      - zrok_env:/mnt
    environment:
      STATE_DIRECTORY: /mnt
      ZROK_ENABLE_TOKEN:
      ZROK_API_ENDPOINT:
      ZROK_ENVIRONMENT_NAME: docker-private-share

  zrok-share-1:
    extends:
      file: zrok-share.yaml
      service: zrok-share
    environment:
      ZROK_TARGET: 'http://localhost:8001'

  zrok-share-2:
    extends:
      file: zrok-share.yaml
      service: zrok-share
    environment:
      ZROK_TARGET: 'http://localhost:8002'

volumes:
  zrok_env:

networks:
  default:

zrok-share.yaml

services:
  zrok-share:
    image: ${ZROK_CONTAINER_IMAGE:-docker.io/openziti/zrok}
    restart: no
    entrypoint:
    - bash
    - -euxc
    - |
      env
      echo "DEBUG: HOME=$${HOME}"
      ls -lA /mnt/.zrok/
      exec zrok share private --headless --backend-mode proxy "$${ZROK_TARGET}"
    depends_on:
      zrok-enable:
        condition: service_completed_successfully
    volumes:
      - zrok_env:/mnt
    environment:
      HOME: /mnt
      PFXLOG_NO_JSON: "true"

Note: In the zrok-share.yaml, I wasn't able to find a way to get ${ZROK_TARGET} to resolve when it's used in the command element unless it's present in the .env file (which defeats the purpose). I think that the command element is resolved at compose file parse time, but variables specified in the environment element are not available for resolution at that point. This is why the whole zrok command is spelled out in the entrypoint element.

2 Likes

Very cool. I hadn't stumbled upon extends until now.