CHECKING STATUS
I AM LISTENING TO
|

Day 27: Expose – Open Source Secure Tunnels – 7 Days of Docker

6. April 2025
.SHARE

Table of Contents

In the world of modern web development, sharing your local work with clients or testing webhooks can be challenging. While tools like ngrok have been the go-to solution for many developers, Expose.dev offers a compelling alternative – especially for PHP developers.

Expose.dev is an elegant open-source tunneling service created by BeyondCode that allows you to generate public URLs for your local applications. What makes it particularly attractive is that you can host the entire infrastructure yourself, giving you complete control over your tunneling service.

In this comprehensive guide, I’ll walk you through setting up your own Expose server using Docker Compose and configuring clients to connect to it.

Why Host Your Own Expose Server?

Before diving into the technical setup, let’s consider why you might want to self-host Expose:

  1. Complete Privacy: Your traffic stays within your infrastructure
  2. Custom Domains: Use your own domain for all tunnels
  3. No Connection Limits: Set your own rules for connection duration and bandwidths

While BeyondCode offers a managed Expose Pro service, self-hosting gives you maximum flexibility and control.

Server Setup with Docker Compose

Setting up your own Expose server is surprisingly straightforward with Docker Compose. Here’s everything you need to get started.

Prerequisites

  • A server with Docker and Docker Compose installed
  • A domain name pointing to your server (for proper SSL)
  • Basic knowledge of Docker and networking

Step 1: Create Your Project Structure

First, let’s create a dedicated directory for our Expose server:

Step 2: Set Up Your Docker Compose Configuration

Now, let’s create the Docker Compose configuration file that will define our Expose server. Create a docker-compose.yml file with the following content:

This configuration:

  • Uses the official Expose server image
  • Maps the container’s port to your host’s port 8080
  • Sets up environment variables for configuration
  • Persists the database to keep your settings and authentication tokens
  • Automatically restarts the container if it crashes or if your server reboots

Step 3: Configure Your Environment Variables

Expose needs a few key configurations to run properly. Create a .env file with your specific settings:

Make sure to replace:

  • yourdomain.com with your actual domain name
  • yourusername and yourpassword with secure credentials for accessing the admin interface

These environment variables are crucial:

  • PORT: The internal port the Expose server will run on
  • DOMAIN: Your domain name, which will be used for all tunneled URLs
  • ADMIN_USERNAME and ADMIN_PASSWORD: Credentials to access the admin dashboard

Step 4: Create the Database Directory

Expose stores its data in an SQLite database. Let’s create a directory for this:

This directory will store user tokens, connection data, and other persistent information.

Step 5: Launch Your Expose Server

Now that everything is set up, it’s time to start your Expose server:

This command starts the Expose server in detached mode, meaning it will run in the background. You can check its status with:

If everything is working correctly, you should see your Expose container running. The default admin can be found under – expose.yourdomain.tld.

Step 6: Setting Up a Reverse Proxy (Recommended)

For maximum security and accessibility, I strongly recommend setting up a reverse proxy with SSL in front of your Expose server. This allows:

  • Secure HTTPS connections
  • Running on standard port 443, which helps bypass most firewalls
  • Proper WebSocket support for real-time updates

Here’s a sample Nginx configuration to get you started:

If you’re using Let’s Encrypt for SSL certificates, you can automate certificate renewal with Certbot. For even more streamlined setup, consider using Traefik as your reverse proxy, which can automatically generate and renew certificates.

Client Configuration: Connecting to Your Custom Server

Once your server is up and running, the next step is configuring your development machines to connect to it. Let’s walk through this process step-by-step.

Step 1: Installing the Expose Client

First, you’ll need to install the Expose client on your development machine. There are two primary installation methods to choose from:

Option 1: Via PHAR file (Recommended for most users)
This method works on any machine with PHP installed and doesn’t require Composer:

Option 2: Via Composer
If you’re a PHP developer and already use Composer, this method integrates well with your existing workflow:

Make sure your global Composer bin directory is in your PATH to access the expose command from anywhere.

If you’re using Laravel Herd, Expose is already included and ready to use.

Step 2: Creating and Publishing Your Configuration File

Expose uses a PHP configuration file to store your settings and server information. To generate this file:

This command creates a default configuration file at ~/.expose/config.php. This file will be the foundation for connecting to your custom server.

You should see a success message showing the path where your configuration file was created. If you’re using a different shell or operating system, the location might vary slightly.

Step 3: Configuring Your Client to Use Your Custom Server

Now comes the crucial part – configuring your client to connect to your self-hosted Expose server instead of the default one.

Open the configuration file in your preferred text editor:

The configuration file contains a ‘servers’ array where you can define multiple server configurations. You’ll need to modify this array to include your custom server:

This configuration does two important things:

  1. It defines your custom server with its domain and port
  2. It sets your custom server as the default, so you don’t have to specify it every time

Step 4: Setting Up Authentication (Recommended)

For added security, I recommend setting up authentication for your Expose server. This prevents unauthorized users from using your tunneling infrastructure.

Here’s how to implement token-based authentication:

  1. Enable Authentication on the Server:
    • Access the admin interface at https://yourdomain.com using the credentials from your .env file
    • Navigate to the Settings section
    • Find and enable the “Validate Auth Tokens” option
    • Create a new user token in the admin interface (note it down for client configuration)
  2. Configure Your Client with the Token:
    The easiest way to set your authentication token is using the built-in command:

This automatically updates your configuration file.

Alternatively, you can manually edit the config file to add the token:

With authentication enabled, only clients with valid tokens can create tunnels through your server, giving you complete control over who can use your infrastructure.

Using Your Self-Hosted Expose Server

Now that you’ve set up both the server and client sides, it’s time to put your custom Expose server to work. Let’s explore the various ways to share your local applications.

Basic Sharing Commands

Here are some common patterns for sharing your sites:

Working with Multiple Server Configurations

One of the powerful features of a self-hosted Expose setup is the ability to work with different server configurations. Here’s how to make the most of this capability:

1. Using Your Default Server

If you’ve set the default_server option in your config file, you can simply run Expose without specifying a server:

This uses the server defined in the ‘default_server’ setting, making the command simpler for your most common use case.

2. Specifying a Server Explicitly

When you need to use a different server than your default:

The server name (custom-server in this example) must match a key in the ‘servers’ array in your config file.

3. Project-Specific Configurations

For teams working on different projects that might need different Expose configurations:

This allows you to maintain separate configurations for different projects or clients.

Advanced Configuration Techniques

As you become more comfortable with your self-hosted Expose setup, you may want to explore more advanced configuration options. Here’s how to fine-tune both the server and client sides.

Enhanced Server Security

Implementing Token-Based Authentication

For team environments, token-based authentication is essential:

  1. Access the admin interface at your domain
  2. Navigate to Settings
  3. Enable “Validate Authentication Tokens”
  4. Create distinct tokens for different team members or projects

This lets you track which team members are using the tunneling service and revoke access when needed.

Custom Subdomain Generation

You can implement custom subdomain patterns by modifying the server’s configuration:

  1. Edit the config/expose.php file inside the Docker container or mount a custom config file
  2. Change the subdomain_generator configuration to implement your own logic
  3. This allows for branded subdomains or meaningful naming patterns

Data Management

Customizing Database Storage

The default SQLite database configuration works well for most deployments, but you can customize it:

This lets you store the database in a different location, which might be useful for backup strategies or when using networked storage.

Request Logging Performance Tuning

For high-traffic scenarios, tune the request logging settings to prevent memory issues:

Advanced Client Options

The client configuration file offers numerous options to customize your experience:

Troubleshooting Common Issues

Even with the most careful setup, you might encounter some issues with your self-hosted Expose environment. Here are solutions to the most common problems developers face.

Connection Problems

Cannot Connect to Server

If your client can’t connect to your Expose server:

  • Domain Resolution: Verify your domain is properly pointing to your server’s IP address
  • Firewall Settings: Ensure your server’s firewall allows connections on the Expose port
  • Client Configuration: Double-check the host and port in your client config match your server
  • Network Rules: Some corporate networks block WebSocket connections; try using port 443 with SSL

Try testing connectivity with a simple curl command:

Subdomain Already Taken

If you get an error about your subdomain being already in use:

  • Someone else is using that subdomain on your server
  • A previous connection might not have been properly terminated
  • Check the admin interface to see active connections and potentially terminate stale ones

SSL Configuration Issues

Certificate Errors

When facing SSL certificate problems:

  • Ensure your SSL certificates are valid and not expired
  • The certificate must match the domain exactly
  • If using Let’s Encrypt, verify the renewal process is working

WebSocket Connection Failures

For WebSocket connection issues:

  • Verify your reverse proxy is properly configured for WebSocket upgrading
  • Check the Upgrade and Connection headers are being properly forwarded
  • Test with a WebSocket client like wscat to isolate the issue

Authentication Problems

Token Not Recognized

If your authentication token isn’t working:

  • Verify the token is correctly set in your client configuration
  • Check that the token exists in the server’s database
  • The server might need to be restarted after adding new tokens
  • Ensure “Validate Authentication Tokens” is enabled in the server settings

A simple test is to use the token command and then check your config file:

Additional Resources

For further exploration and assistance:

Thoughts

Setting up your own Expose server using Docker Compose provides a powerful, customizable alternative to services like ngrok. With complete control over your tunneling infrastructure, you can ensure privacy, customize domains, and manage team access as needed.

While the initial setup requires some technical knowledge, the benefits of self-hosting – especially for teams – make it well worth the effort. As your development workflows evolve, you can continue to refine your Expose configuration to match your specific needs.

For developers who prefer a managed solution with many of the same benefits, consider Expose Pro, which offers a globally distributed network without the maintenance overhead.

Whether you choose the self-hosted route or the managed service, Expose represents a PHP-native, developer-friendly approach to solving the local tunnel challenge.

Let’s Talk!

Looking for a reliable partner to bring your project to the next level? Whether it’s development, design, security, or ongoing support—I’d love to chat and see how I can help.

Get in touch,
and let’s create something amazing together!

RELATED POSTS

FrankenWP is a specialized WordPress Docker image built on FrankenPHP, which is a PHP application server built on top of the Caddy web server. This combination offers several advantages: This guide will walk you through setting up FrankenWP on your own server using Docker Compose, including all necessary configuration options and client connection details. Also […]

Remember when people used to joke that PHP was dying? Well, in 2025, PHP is not only alive and kicking but thriving thanks to its Frankenstein-inspired application server that’s been taking the web development world by storm! What Is This Monster? FrankenPHP is the brainchild of Kévin Dunglas (the same genius behind API Platform) who […]

Hey there! Ever wondered how websites know when you’re actually looking at them, or if you’ve wandered off to make coffee? That’s presence detection in action – and it’s super useful for creating responsive, user-friendly web apps. In this guide, I’ll walk you through everything you need to know about detecting user presence with JavaScript […]

Alexander

I am a full-stack developer. My expertise include:

  • Server, Network and Hosting Environments
  • Data Modeling / Import / Export
  • Business Logic
  • API Layer / Action layer / MVC
  • User Interfaces
  • User Experience
  • Understand what the customer and the business needs


I have a deep passion for programming, design, and server architecture—each of these fuels my creativity, and I wouldn’t feel complete without them.

With a broad range of interests, I’m always exploring new technologies and expanding my knowledge wherever needed. The tech world evolves rapidly, and I love staying ahead by embracing the latest innovations.

Beyond technology, I value peace and surround myself with like-minded individuals.

I firmly believe in the principle: Help others, and help will find its way back to you when you need it.