Skip to main content

Embedding Temporal server as a Go library

You can run Temporal server as an embedded Go library instead of deploying it as a separate service. This approach is useful for testing and development scenarios where you want to run Temporal in-process without managing external infrastructure.

Not for production use

Embedded deployments with SQLite are suitable for testing and development only. For production workloads, deploy Temporal as a service using MySQL, PostgreSQL, or Cassandra as the persistence layer.

Reference implementation

The recommended way to run an embedded Temporal server is to use the Temporal CLI's dev server implementation as a reference. The CLI's devserver package provides a complete implementation that handles:

  • SQLite configuration and schema setup
  • Namespace creation
  • Service configuration
  • Port allocation

You can study and adapt this implementation for your own embedded use case.

Basic server API

The core API for embedding Temporal is temporal.NewServer():

import (
"go.temporal.io/server/temporal"
"go.temporal.io/server/common/config"
)

server, err := temporal.NewServer(
temporal.ForServices(temporal.DefaultServices),
temporal.WithConfig(cfg),
temporal.InterruptOn(temporal.InterruptCh()),
)
if err != nil {
log.Fatal(err)
}

if err := server.Start(); err != nil {
log.Fatal(err)
}

The challenge is building the config.Config struct correctly, especially for SQLite which requires:

  1. Schema setup - SQLite databases need schema initialization via sqliteschema.SetupSchema()
  2. Namespace creation - Namespaces can be pre-created via sqliteschema.CreateNamespaces()
  3. Service configuration - All four services (frontend, history, matching, worker) need proper port configuration

Configuration from file

For non-SQLite databases, you can load configuration from a YAML file:

cfg, err := config.Load(
config.WithConfigFile("/path/to/config.yaml"),
)
if err != nil {
log.Fatal(err)
}

server, err := temporal.NewServer(
temporal.ForServices(temporal.DefaultServices),
temporal.WithConfig(cfg),
)

Or load from a directory with environment-specific files:

cfg, err := config.Load(
config.WithConfigDir("./config"),
config.WithEnv("development"),
)

Server options reference

The temporal.NewServer() function accepts options to customize the server. See Server Options Reference for the complete list.

Key options include:

OptionDescription
ForServices([]string)Services to run (default: frontend, history, matching, worker)
WithConfig(*config.Config)Server configuration
WithLogger(log.Logger)Custom logger
WithAuthorizer(authorization.Authorizer)Custom authorization
WithClaimMapper(func)Role/claim mapping for auth
WithCustomMetricsHandler(metrics.Handler)Custom metrics handler
WithDynamicConfigClient(dynamicconfig.Client)Runtime configuration
InterruptOn(chan)Channel for graceful shutdown

SQLite limitations

SQLite is intended for testing and development only:

  • Single writer: SQLite supports only one writer at a time, limiting write throughput
  • No durability in memory mode: In-memory mode loses data on restart
  • Not scalable: Cannot handle production workloads
  • Single shard: Use NumHistoryShards: 1 for SQLite

For production, use MySQL, PostgreSQL, or Cassandra with a properly scaled multi-node deployment.

Examples

For complete working examples, see: