Skip to content

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

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

Terminal window
# 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).

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",
},
},

A health check endpoint is automatically available at /health:

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

This is useful for:

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

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 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);
});

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
FeatureDesktop ModeServer Mode
Native windowsCreatedBrowser windows (browser-N)
System trayAvailableNot available
Native dialogsAvailableNot available
Application menuAvailableNot available
Screen infoAvailableReturns error
Service bindingsWorksWorks
EventsWorksWorks (via WebSocket)
AssetsVia webviewVia HTTP
CGO requiredYesNo

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.

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

Terminal window
# Build for server mode
wails3 task build:server
# Build and run
wails3 task run:server
Terminal window
# Build with server mode
go build -tags server -o myapp-server .

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

Terminal window
# 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:

Terminal window
# 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.

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

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"]

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

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

Terminal window
cd v3/examples/server
# Using Taskfile
task dev
# Or run directly
go run -tags server .
# Open http://localhost:8080 in browser

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

VariableDescriptionDefault
WAILS_SERVER_HOSTNetwork interface to bind tolocalhost
WAILS_SERVER_PORTPort to listen on8080

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.