def launch_image(
        # players info
        player: Player,
        nth_player: int,
        num_players: int,

        # game settings
        headless: bool,
        game_name: str,
        map_name: str,
        game_type: GameType,
        game_speed: int,
        timeout: Optional[int],
        hide_names: bool,
        random_names: bool,
        drop_players: bool,
        allow_input: bool,
        auto_launch: bool,

        # mount dirs
        game_dir: str,
        bot_dir: str,
        map_dir: str,
        bwapi_data_bwta_dir: str,
        bwapi_data_bwta2_dir: str,

        vnc_base_port: int,
        vnc_host: int,
        capture_movement: bool,

        # docker
        docker_image: str,
        docker_opts: List[str]
) -> None:
    """
    :raises docker,errors.APIError
    :raises DockerException
    """
    container_name = f"{game_name}_{nth_player}_{player.name.replace(' ', '_')}"

    log_dir = f"{game_dir}/{game_name}/logs_{nth_player}"
    crashes_dir = f"{game_dir}/{game_name}/crashes_{nth_player}"
    os.makedirs(log_dir, mode=0o777, exist_ok=True)  # todo: proper mode
    os.makedirs(crashes_dir, mode=0o777, exist_ok=True)  # todo: proper mode

    volumes = {
        xoscmounts(log_dir): {"bind": LOG_DIR, "mode": "rw"},
        xoscmounts(map_dir): {"bind": MAP_DIR, "mode": "rw"},
        xoscmounts(crashes_dir): {"bind": ERRORS_DIR, "mode": "rw"},
        xoscmounts(bwapi_data_bwta_dir): {"bind": BWAPI_DATA_BWTA_DIR, "mode": "rw"},
        xoscmounts(bwapi_data_bwta2_dir): {"bind": BWAPI_DATA_BWTA2_DIR, "mode": "rw"},
    }

    ports = {}
    if not headless:
        ports.update({"5900/tcp": vnc_base_port + nth_player})

    env = dict(
        PLAYER_NAME=player.name if not random_names else random_string(8),
        PLAYER_RACE=player.race.value,
        NTH_PLAYER=nth_player,
        NUM_PLAYERS=num_players,
        GAME_NAME=game_name,
        MAP_NAME=f"/app/sc/maps/{map_name}",
        GAME_TYPE=game_type.value,
        SPEED_OVERRIDE=game_speed,
        HIDE_NAMES="1" if hide_names else "0",
        DROP_PLAYERS="1" if drop_players else "0",

        TM_LOG_RESULTS=f"../logs/scores.json",
        TM_LOG_FRAMETIMES=f"../logs/frames.csv",
        TM_SPEED_OVERRIDE=game_speed,
        TM_ALLOW_USER_INPUT="1" if isinstance(player, HumanPlayer) or allow_input else "0",

        EXIT_CODE_REALTIME_OUTED=EXIT_CODE_REALTIME_OUTED,
        CAPTURE_MOUSE_MOVEMENT="1" if capture_movement else "0",
        HEADFUL_AUTO_LAUNCH="1" if auto_launch else "0",

        JAVA_DEBUG="0"
    )

    if timeout is not None:
        env["PLAY_TIMEOUT"] = timeout

    if isinstance(player, BotPlayer):
        # Only mount write directory, read and AI
        # are copied from the bot directory in proper places in bwapi-data
        bot_data_write_dir = f"{game_dir}/{game_name}/write_{nth_player}/"
        os.makedirs(bot_data_write_dir, mode=0o777, exist_ok=True)  # todo: proper mode
        volumes.update({
            xoscmounts(bot_data_write_dir): {"bind": BOT_DATA_WRITE_DIR, "mode": "rw"},
            xoscmounts(player.bot_dir): {"bind": BOT_DIR, "mode": "ro"},
        })
        env["BOT_FILE"] = player.bot_basefilename
        env["BOT_BWAPI"] = player.bwapi_version

        env["JAVA_DEBUG"] = "0"
        env["JAVA_DEBUG_PORT"] = ""
        env["JAVA_OPTS"] = ""

        command = ["/app/play_bot.sh"]
        if player.meta.javaDebugPort is not None:
            ports.update({"player.meta.javaDebugPort/tcp": player.meta.javaDebugPort})
            env["JAVA_DEBUG"] = "1"
            env["JAVA_DEBUG_PORT"] = player.meta.javaDebugPort
        if player.meta.javaOpts is not None:
            env["JAVA_OPTS"] = player.meta.javaOpts
        if player.meta.port is not None:
            if isinstance(player.meta.port, int) or player.meta.port.isdigit():
                ports.update({str(player.meta.port) + '/tcp': int(player.meta.port)})
            else:
                forward, local = [int(x) for x in player.meta.port.split(':')]
                ports.update({str(local) + '/tcp': forward})
    else:
        command = ["/app/play_human.sh"]

    is_server = nth_player == 0

    entrypoint_opts = ["--headful"]
    if headless:
        entrypoint_opts = [
            "--game", game_name, "--name", player.name,
            "--race", player.race.value, "--lan"
        ]
        if is_server:
            entrypoint_opts += ["--host", "--map", f"/app/sc/maps/{map_name}"]
        else:
            entrypoint_opts += ["--join"]
    command += entrypoint_opts

    logger.debug(
        "\n"
        f"docker_image={docker_image}\n"
        f"command={pformat(command, indent=4)}\n"
        f"name={container_name}\n"
        f"detach={True}\n"
        f"environment={pformat(env, indent=4)}\n"
        f"privileged={True}\n"
        f"volumes={pformat(volumes, indent=4)}\n"
        f"network={DOCKER_STARCRAFT_NETWORK}\n"
        f"ports={ports}\n"
    )

    container = docker_client.containers.run(
        docker_image,
        command=command,
        name=container_name,
        detach=True,
        environment=env,
        privileged=True,
        volumes=volumes,
        network=DOCKER_STARCRAFT_NETWORK,
        ports=ports
    )
    if container:
        container_id = running_containers(container_name)
        logger.info(f"launched {player}")
        logger.debug(f"container name = '{container_name}', container id = '{container_id}'")
    else:
        raise DockerException(f"could not launch {player} in container {container_name}")
:raises docker,errors.APIError
    :raises DockerException