Server Build

Wails v3 supports server mode, allowing you to run your application as a pure HTTP server without creating native windows or requiring GUI dependencies. This enables deploying the same Wails application to servers, containers, and web browsers.

Server mode is useful for:

  • Docker/Container deployments - Run without X11/Wayland dependencies
  • Server-side applications - Deploy as a web server accessible via browser
  • Web-only access - Share the same codebase between desktop and web
  • CI/CD testing - Run integration tests without a display server
  • Microservices - Use Wails bindings in headless backend services

Quick Start

Server mode is enabled via the server build tag. Your application code remains the same - you just build with the tag:

bash
# Using Taskfile (recommended)
wails3 task build:server
wails3 task run:server

# Or build directly with Go
go build -tags server -o myapp-server .

Here’s a minimal example:

package main

import (
    "embed"
    "log"

    "github.com/wailsapp/wails/v3/pkg/application"
)

//go:embed frontend/dist
var assets embed.FS

func main() {
    app := application.New(application.Options{
        Name: "My App",
        // Server options are used when built with -tags server
        Server: application.ServerOptions{
            Host: "localhost",
            Port: 8080,
        },
        Services: []application.Service{
            application.NewService(&MyService{}),
        },
        Assets: application.AssetOptions{
            Handler: application.AssetFileServerFS(assets),
        },
    })

    log.Println("Starting application...")
    if err := app.Run(); err != nil {
        log.Fatal(err)
    }
}

The same code can be built for desktop (without the tag) or server mode (with -tags server).

Configuration

ServerOptions

Configure the HTTP server with ServerOptions:

Server: application.ServerOptions{
    // Host to bind to. Default: "localhost"
    // Use "0.0.0.0" to listen on all interfaces
    Host: "localhost",

    // Port to listen on. Default: 8080
    Port: 8080,

    // Request read timeout. Default: 30s
    ReadTimeout: 30 * time.Second,

    // Response write timeout. Default: 30s
    WriteTimeout: 30 * time.Second,

    // Idle connection timeout. Default: 120s
    IdleTimeout: 120 * time.Second,

    // Graceful shutdown timeout. Default: 30s
    ShutdownTimeout: 30 * time.Second,

    // TLS configuration (optional)
    TLS: &application.TLSOptions{
        CertFile: "/path/to/cert.pem",
        KeyFile:  "/path/to/key.pem",
    },
},

Features

Health Check Endpoint

A health check endpoint is automatically available at /health:

bash
curl http://localhost:8080/health
# {"status":"ok"}

This is useful for:

  • Kubernetes liveness/readiness probes
  • Load balancer health checks
  • Monitoring systems

Service Bindings

All service bindings work identically to desktop mode:

type GreetService struct{}

func (g *GreetService) Greet(name string) string {
    return "Hello, " + name + "!"
}

// Register in options
Services: []application.Service{
    application.NewService(&GreetService{}),
},

The frontend can call these bindings using the standard Wails runtime:

const greeting = await wails.Call.ByName('main.GreetService.Greet', 'World');

Events

Events work bidirectionally in server mode:

  • Frontend to Backend: Events emitted from the browser are sent via HTTP and received by your Go event handlers
  • Backend to Frontend: Events emitted from Go are broadcast to all connected browsers via WebSocket

Each browser tab is represented as a “window” with a unique name (browser-1, browser-2, etc.), accessible via event.Sender:

// Listen for events from browsers
app.Event.On("user-action", func(event *application.CustomEvent) {
    log.Printf("Event from %s: %v", event.Sender, event.Data)
    // event.Sender will be "browser-1", "browser-2", etc.
})

// Emit events to all connected browsers
app.Event.Emit("server-update", data)

From the frontend:

// Emit event to server (and all other browsers)
await wails.Events.Emit('user-action', { action: 'click' });

// Listen for events from server
wails.Events.On('server-update', (event) => {
    console.log('Update from server:', event.data);
});

Graceful Shutdown

The server handles SIGINT and SIGTERM signals gracefully:

  1. Stops accepting new connections
  2. Waits for active requests to complete (up to ShutdownTimeout)
  3. Runs OnShutdown hooks
  4. Shuts down services in reverse order

Differences from Desktop Mode

Feature Desktop Mode Server Mode
Native windows Created Browser windows (browser-N)
System tray Available Not available
Native dialogs Available Not available
Application menu Available Not available
Screen info Available Returns error
Service bindings Works Works
Events Works Works (via WebSocket)
Assets Via webview Via HTTP
CGO required Yes No

Window API Behavior

In server mode, window-related APIs are safely handled:

  • app.Window.NewWithOptions() - Logs a warning, returns nil
  • app.Hide() / app.Show() - No-op
  • app.Screen.GetPrimary() - Returns error

This allows code that references windows to run without crashing, though window operations have no effect.

Building for Production

Projects created with wails3 init include a build:server task:

bash
# Build for server mode
wails3 task build:server

# Build and run
wails3 task run:server

Manual Build

bash
# Build with server mode
go build -tags server -o myapp-server .

Docker

Wails projects include a ready-to-use Docker setup. To build and run your application in a container:

bash
# Build the Docker image
wails3 task build:docker

# Run it
wails3 task run:docker

That’s it! Your application will be available at http://localhost:8080.

You can customise the build with a few options:

bash
# Use a custom image tag
wails3 task build:docker TAG=myapp:v1.0.0

# Run on a different port
wails3 task run:docker PORT=3000

The generated Dockerfile.server creates a minimal image based on distroless. It handles the network binding automatically, so your application will be accessible from outside the container.

Docker Compose

For more complex deployments, here’s a Docker Compose configuration with health checks:

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - WAILS_SERVER_HOST=0.0.0.0
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

Custom Dockerfile

If you need more control, you can create your own Dockerfile. The key thing to remember is setting WAILS_SERVER_HOST=0.0.0.0 so the server accepts connections from outside the container:

# Build stage
FROM golang:alpine AS builder
WORKDIR /app
RUN apk add --no-cache git
COPY . .
RUN go mod tidy
RUN go build -tags server -ldflags="-s -w" -o server .

# Runtime stage
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
COPY --from=builder /app/frontend/dist /frontend/dist
EXPOSE 8080
ENV WAILS_SERVER_HOST=0.0.0.0
ENTRYPOINT ["/server"]

Security Considerations

When deploying server mode applications:

  1. Bind to localhost by default - Only use 0.0.0.0 when needed
  2. Use TLS in production - Configure ServerOptions.TLS
  3. Place behind reverse proxy - Use nginx/traefik for additional security
  4. Validate all inputs - Same security practices as any web application

Example

A complete example is available at v3/examples/server/:

bash
cd v3/examples/server

# Using Taskfile
task dev

# Or run directly
go run -tags server .

# Open http://localhost:8080 in browser

Environment Variables

For deployment scenarios where you need to override the server configuration without changing code, Wails recognises these environment variables:

Variable Description Default
WAILS_SERVER_HOST Network interface to bind to localhost
WAILS_SERVER_PORT Port to listen on 8080

These take precedence over the ServerOptions in your code, which is why the Docker examples set WAILS_SERVER_HOST=0.0.0.0 - it allows the container to accept external connections without requiring any changes to your application.

See Also

Edit page

Last updated: