Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@
"axios": "^1.13.2",
"zustand": "^5.0.9"
}
}
}
9 changes: 5 additions & 4 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { encodeMessage, decodeMessage } from "./protocol";
import type {
OutrayClientOptions,
TunnelDataMessage,
TunnelResponseMessage,
ErrorCodes,
TunnelResponseMessage
} from "./types";

const DEFAULT_SERVER_URL = "wss://api.outray.dev/";
const DEFAULT_LOCAL_HOST = "localhost";
const PING_INTERVAL_MS = 25000;
const PONG_TIMEOUT_MS = 10000;

Expand All @@ -35,7 +35,7 @@ const PONG_TIMEOUT_MS = 10000;
export class OutrayClient {
private ws: WebSocket | null = null;
private options: Required<
Pick<OutrayClientOptions, "localPort" | "serverUrl">
Pick<OutrayClientOptions, "localPort" | "serverUrl" | "localHost">
> &
OutrayClientOptions;
private reconnectTimeout: NodeJS.Timeout | null = null;
Expand All @@ -52,6 +52,7 @@ export class OutrayClient {
this.options = {
...options,
serverUrl: options.serverUrl ?? DEFAULT_SERVER_URL,
localHost: options.localHost ?? DEFAULT_LOCAL_HOST,
};
this.subdomain = options.subdomain;
}
Expand Down Expand Up @@ -174,7 +175,7 @@ export class OutrayClient {
const startTime = Date.now();

const reqOptions = {
hostname: "localhost",
hostname: this.options.localHost,
port: this.options.localPort,
path: message.path,
method: message.method,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ export interface OutrayClientOptions {
*/
localPort: number;

/**
* Local host to proxy requests to
* @default 'localhost'
*/
localHost?: string;

/**
* Outray server WebSocket URL
* @default 'wss://api.outray.dev/'
Expand Down
2 changes: 2 additions & 0 deletions packages/docker-extension/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ui/node_modules
backend/node_modules
3 changes: 3 additions & 0 deletions packages/docker-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
ui/build
backend/dist
49 changes: 49 additions & 0 deletions packages/docker-extension/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Backend
FROM node:22-alpine AS backend-builder
WORKDIR /backend
COPY ./backend/package.json .
COPY ./backend/package-lock.json .
RUN npm install
COPY ./backend/. .

RUN --mount=type=cache,target=/usr/src/app/.npm \
npm set cache /usr/src/app/.npm && \
npm run build

# UI
FROM --platform=$BUILDPLATFORM node:22-alpine AS client-builder
WORKDIR /ui
COPY ./ui/package.json .
COPY ./ui/package-lock.json .
RUN --mount=type=cache,target=/usr/src/app/.npm \
npm set cache /usr/src/app/.npm && \
npm ci
COPY ./ui .
RUN npm run build


# Final stage
FROM node:22-alpine

LABEL org.opencontainers.image.title="Outray" \
org.opencontainers.image.description="Expose your containers to the internet" \
org.opencontainers.image.vendor="Outray" \
com.docker.desktop.extension.api.version="0.4.2" \
com.docker.extension.screenshots="" \
com.docker.desktop.extension.icon="logo.svg" \
com.docker.extension.detailed-description="" \
com.docker.extension.publisher-url="https://outray.dev" \
com.docker.extension.additional-urls="https://github.com/outray-tunnel" \
com.docker.extension.categories="Networking" \
com.docker.extension.changelog=""

COPY --from=backend-builder /backend/dist /backend/dist
COPY --from=backend-builder /backend/node_modules /backend/node_modules
COPY ./docker-compose.yaml .
COPY ./metadata.json .
COPY ./logo.svg .
COPY --from=client-builder /ui/build ui

CMD ["node", "/backend/dist/main.js", "-socket", "/run/guest-services/backend.sock"]


28 changes: 28 additions & 0 deletions packages/docker-extension/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
IMAGE?=outray/outray-extension
TAG?=0.0.1

BUILDER=buildx-multi-arch

INFO_COLOR = \033[0;36m
NO_COLOR = \033[m

build-extension: ## Build service image to be deployed as a desktop extension
docker build --tag=$(IMAGE):$(TAG) -f Dockerfile .

install-extension: build-extension ## Install the extension
docker extension install $(IMAGE):$(TAG)

update-extension: build-extension ## Update the extension
docker extension update $(IMAGE):$(TAG)

prepare-buildx: ## Create buildx builder for multi-arch build, if not exists
docker buildx inspect $(BUILDER) || docker buildx create --name=$(BUILDER) --driver=docker-container --driver-opt=network=host

push-extension: prepare-buildx ## Build & Upload extension image to hub. Do not push if tag already exists: make push-extension tag=0.1
docker pull $(IMAGE):$(TAG) && echo "Failure: Tag already exists" || docker buildx build --push --builder=$(BUILDER) --platform=linux/amd64,linux/arm64 --build-arg TAG=$(TAG) --tag=$(IMAGE):$(TAG) .

help: ## Show this help
@echo Please specify a build target. The choices are:
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(INFO_COLOR)%-30s$(NO_COLOR) %s\n", $$1, $$2}'

.PHONY: help
85 changes: 85 additions & 0 deletions packages/docker-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Outray

Expose your local containers to the internet securely with [Outray](https://outray.dev).

This Docker Extension allows you to create public tunnels for your running containers directly from Docker Desktop.

## Features

- **One-click Expose**: Generate public URLs for your local containers.
- **Secure**: API Key authentication and secure tunnel establishment.
- **Persistent**: Settings and tunnels are saved across restarts.

## Local development

You can use `docker` to build, install and push your extension. Also, we provide an opinionated [Makefile](Makefile) that could be convenient for you. There isn't a strong preference of using one over the other, so just use the one you're most comfortable with.

To build the extension, use `make build-extension` **or**:

```shell
docker buildx build -t outray/outray-extension:latest . --load
```

To install the extension, use `make install-extension` **or**:

```shell
docker extension install outray/outray-extension:latest
```

> If you want to automate this command, use the `-f` or `--force` flag to accept the warning message.

To preview the extension in Docker Desktop, open Docker Dashboard once the installation is complete. The left-hand menu displays a new tab with the name of your extension. You can also use `docker extension ls` to see that the extension has been installed successfully.

### Frontend development

During the development of the frontend part, it's helpful to use hot reloading to test your changes without rebuilding your entire extension. To do this, you can configure Docker Desktop to load your UI from a development server.
Assuming your app runs on the default port, start your UI app and then run:

```shell
cd ui
npm install
npm run dev
```

This starts a development server that listens on port `3000`.

You can now tell Docker Desktop to use this as the frontend source. In another terminal run:

```shell
docker extension dev ui-source outray/outray-extension:latest http://localhost:3000
```

In order to open the Chrome Dev Tools for your extension when you click on the extension tab, run:

```shell
docker extension dev debug outray/outray-extension:latest
```

Each subsequent click on the extension tab will also open Chrome Dev Tools. To stop this behaviour, run:

```shell
docker extension dev reset outray/outray-extension:latest
```

### Backend development (optional)

The backend runs a Node.js/Express API that manages the tunnels and persists configuration to a SQLite database. It communicates with the frontend via the Docker Extension socket.

While extensions don't strictly require a backend, Outray uses one to handle secure connections and data persistence reliably.

Whenever you make changes in the [backend](./backend) source code, you will need to compile them and re-deploy a new version of your backend container.
Use the `docker extension update` command to remove and re-install the extension automatically:

```shell
docker extension update outray/outray-extension:latest
```

> Note: The database will persist across updates. To reset it, you must remove the extension and its volume.

## Clean Up

To remove the extension:

```shell
docker extension rm outray/outray-extension:latest
```
1 change: 1 addition & 0 deletions packages/docker-extension/backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DB_FILE_NAME="file:./outray.db"
11 changes: 11 additions & 0 deletions packages/docker-extension/backend/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Config } from "drizzle-kit";
import { DB_PATH } from "./src/db";

export default {
schema: "./src/db/schema.ts",
out: "./src/drizzle",
dialect: "sqlite",
dbCredentials: {
url: DB_PATH,
},
} satisfies Config;
Loading