Container Deployment of Copilot SDK with Kubernetes and Persistent Storage: A Complete Guide
To deploy the GitHub Copilot SDK in Kubernetes with persistent storage, implement the SessionFSProvider interface to write session data to a mounted volume, then wire a PersistentVolumeClaim (PVC) to your container to ensure files and SQLite databases survive pod restarts.
The GitHub Copilot SDK is a language-agnostic library that embeds AI-driven assistants into applications. When running container deployment of the Copilot SDK with Kubernetes and persistent storage, you need session data—such as files, tool result caches, and SQLite databases—to persist beyond the lifecycle of individual pods. This is achieved by implementing provider interfaces defined in the SDK and mounting a Kubernetes PVC to a stable filesystem path.
Understanding the Persistence Architecture
The Copilot SDK delegates all storage operations to pluggable providers rather than handling persistence internally. This design allows the same codebase to run ephemerally in development and statefully in production Kubernetes clusters.
The SessionFSProvider Interface
At the heart of the persistence mechanism is the SessionFSProvider interface defined in go/session_fs_provider.go. This contract requires implementations of standard filesystem operations:
ReadFile(path string) (string, error)WriteFile(path, content string, mode *int) errorMakeDirectory(path string) errorReadDirectory(path string) ([]copilot.SessionFSDirectoryItem, error)
When your application creates a copilot.Client, you inject a provider instance that translates these method calls into actual disk operations. According to the source code in session_fs_provider.go, the SDK uses this abstraction to remain agnostic about underlying storage mechanisms.
Optional SQLite Support
For applications storing structured data like tool histories or vector embeddings, the SDK supports an optional SessionFSSqliteProvider interface—defined in the same session_fs_provider.go file. This extension adds:
SqliteExists() (bool, error)– Checks for a database file at the configured rootSqliteQuery(qt rpc.SessionFSSqliteQueryType, query string, params map[string]any) (*copilot.SessionFSSqliteQueryResult, error)– Executes SQLite operations
The provider determines the database location. When mounted to a PVC, the SQLite file survives pod termination and rescheduling.
Building the Container Image
Start with a multi-stage Dockerfile that compiles the SDK binary and exposes a writable directory for the PVC mount. The following configuration uses Go 1.22 and creates a minimal Alpine-based image:
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/copilot-sdk ./go/main.go
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY --from=builder /bin/copilot-sdk /usr/local/bin/copilot-sdk
# The SDK expects a writable directory at /workspace (mounted via PVC)
VOLUME /workspace
ENTRYPOINT ["/usr/local/bin/copilot-sdk"]
This image expects a writable /workspace directory where the provider will store files and SQLite databases.
Implementing the Storage Provider
Create a diskProvider struct that implements both SessionFSProvider and SessionFSSqliteProvider, writing to a configurable root path matching your PVC mount point. Below is a minimal implementation:
package main
import (
"database/sql"
"os"
"path/filepath"
"github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
_ "modernc.org/sqlite"
)
// diskProvider implements both SessionFSProvider and SessionFSSqliteProvider.
type diskProvider struct {
root string // e.g., "/workspace"
}
// Helper to resolve absolute paths.
func (p *diskProvider) resolve(pth string) string { return filepath.Join(p.root, pth) }
func (p *diskProvider) ReadFile(path string) (string, error) {
b, err := os.ReadFile(p.resolve(path))
if err != nil {
return "", err
}
return string(b), nil
}
func (p *diskProvider) WriteFile(path, content string, mode *int) error {
full := p.resolve(path)
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
return err
}
perm := os.FileMode(0o644)
if mode != nil {
perm = os.FileMode(*mode)
}
return os.WriteFile(full, []byte(content), perm)
}
// SQLite support – the DB lives at "<root>/session.db".
func (p *diskProvider) SqliteExists() (bool, error) {
_, err := os.Stat(p.resolve("session.db"))
if os.IsNotExist(err) {
return false, nil
}
return err == nil, err
}
func (p *diskProvider) SqliteQuery(qt rpc.SessionFSSqliteQueryType, query string, params map[string]any) (*copilot.SessionFSSqliteQueryResult, error) {
db, err := sql.Open("sqlite", p.resolve("session.db"))
if err != nil {
return nil, err
}
defer db.Close()
// Execute query and marshal results into SessionFSSqliteQueryResult.
// Reference session_fs_sqlite_e2e_test.go for full transaction handling.
return nil, nil
}
This implementation mirrors the signatures found in session_fs_provider.go and the SQLite transaction logic demonstrated in session_fs_sqlite_e2e_test.go.
Wiring Everything Together
Initialize the Copilot client with your provider implementation, pointing the root to /workspace to align with the Kubernetes volume mount:
package main
import (
"context"
"log"
"time"
"github.com/github/copilot-sdk/go"
)
func main() {
provider := &diskProvider{root: "/workspace"}
client, err := copilot.NewClient(
copilot.WithSessionFSProvider(provider),
copilot.WithSessionFSSqliteProvider(provider),
)
if err != nil {
log.Fatalf("client init: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
sess, err := client.NewSession(ctx, copilot.SessionConfig{})
if err != nil {
log.Fatalf("session: %v", err)
}
// This file persists across pod restarts because it writes to the PVC-backed directory.
if err := sess.WriteFile(ctx, "welcome.txt", "Hello from Copilot SDK!", nil); err != nil {
log.Fatalf("write: %v", err)
}
log.Println("session started; file written to persistent volume")
}
When this binary runs inside a container, welcome.txt is written to /workspace/welcome.txt on the PVC-backed filesystem.
Kubernetes Deployment Configuration
Deploy the application using a PersistentVolumeClaim for durability and a Deployment that mounts the volume at the expected path:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: copilot-sdk-pvc
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: copilot-sdk
spec:
replicas: 1
selector:
matchLabels:
app: copilot-sdk
template:
metadata:
labels:
app: copilot-sdk
spec:
containers:
- name: sdk
image: ghcr.io/github/copilot-sdk:latest
volumeMounts:
- name: workspace
mountPath: /workspace
env:
- name: COPILOT_API_ENDPOINT
value: "https://api.githubcopilot.com"
volumes:
- name: workspace
persistentVolumeClaim:
claimName: copilot-sdk-pvc
- The PVC guarantees that
/workspacesurvives pod termination withReadWriteOnceaccess mode suitable for single-replica deployments. - The Deployment mounts the PVC at
/workspace, matching thediskProvider.rootconfiguration. - When you update the container image (e.g., adding new tool definitions), existing session files and the SQLite database remain intact on the persistent volume.
Summary
- Implement
SessionFSProviderto give the Copilot SDK a filesystem interface that reads and writes to a local path. - Optionally implement
SessionFSSqliteProviderto enable SQLite persistence for structured session data like tool histories. - Mount a PersistentVolumeClaim at
/workspace(or your provider's root path) to ensure data survives pod restarts, rescheduling, and image updates. - Reference implementation files include
go/session_fs_provider.gofor interface definitions andgo/internal/e2e/session_fs_sqlite_e2e_test.gofor SQLite transaction patterns. - Scale safely by maintaining the PVC separately from the Deployment, allowing you to update the SDK binary without losing user session data.
Frequently Asked Questions
How do I implement SessionFSProvider for the Copilot SDK?
You create a struct that implements methods like ReadFile, WriteFile, and MakeDirectory, then inject it via copilot.WithSessionFSProvider() when constructing the client. The interface is defined in go/session_fs_provider.go, and your implementation should handle path resolution relative to your PVC mount point.
Does the Copilot SDK require SQLite for session persistence?
No, SQLite is optional. If your application only needs to store files (text, JSON, binary data), implementing SessionFSProvider alone is sufficient. You only need to implement SessionFSSqliteProvider if your tools require relational data storage or vector operations using SQLite, as demonstrated in the SDK's session_fs_sqlite_e2e_test.go examples.
What happens to session data when a pod restarts in Kubernetes?
If you have mounted a PersistentVolumeClaim at the provider's root path (e.g., /workspace), session data—including files written via WriteFile and SQLite databases—survives the restart because the PVC exists independently of the pod lifecycle. Without a PVC, the container filesystem is ephemeral and all session data is lost when the pod terminates.
Can I scale the Copilot SDK Deployment to multiple replicas?
Caution is required when scaling beyond one replica. If using ReadWriteOnce PVC access mode (as shown in the example), the volume can only mount to one node at a time, limiting you to a single replica or requiring a pod distribution strategy. For multi-replica deployments, use ReadWriteMany access mode with a compatible storage class (like NFS or EFS), or switch to a stateless architecture where each replica manages its own ephemeral storage.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →