Setting up Atuin server on K8S

Setting up Atuin server on K8S

August 28, 2025

Atuin is a nice tool to sync, search and backup shell history between machines. This post describes how to set up an Atuin server on Kubernetes with PostgreSQL Operator and is a follow-up to the previous post.

Prerequisites

Before starting, you’ll need:

  1. A running Kubernetes cluster with admin access
  2. kubectl configured to access your cluster
  3. PostgreSQL Operator installed in your cluster

Installing PostgreSQL Operator

The PostgreSQL Operator must be installed before deploying the Atuin server. You can install it using the official Zalando PostgreSQL Operator:

# First clone the repository
git clone https://github.com/zalando/postgres-operator.git
cd postgres-operator

# Apply the manifests in the correct order
kubectl create -f manifests/configmap.yaml
kubectl create -f manifests/operator-service-account-rbac.yaml
kubectl create -f manifests/postgres-operator.yaml
kubectl create -f manifests/api-service.yaml

Alternatively, use kustomization (requires kubectl 1.14+):

kubectl apply -k github.com/zalando/postgres-operator/manifests

For detailed installation instructions, configuration options, and troubleshooting, visit the PostgreSQL Operator documentation.

Architecture Overview

The setup consists of four main Kubernetes resources that work together to provide a complete Atuin server deployment:

  1. PostgreSQL Database - Using the PostgreSQL Operator for managed database
  2. Deployment - The main Atuin server application
  3. Service - Network access to the Atuin server
  4. Ingress - External access and TLS termination (optional for production)

Let’s examine each configuration in detail.

PostgreSQL Database Configuration

First, we need to set up a PostgreSQL database using the PostgreSQL Operator. This provides a managed database instance with automatic backups, monitoring, and high availability:

apiVersion: "acid.zalan.do/v1"
kind: postgresql
metadata:
  name: example-atuin
spec:
  teamId: "example"
  volume:
    size: 5Gi
  numberOfInstances: 2
  users:
    atuin:
      - superuser
      - createdb
  databases:
    atuin: atuin
  postgresql:
    version: "16"

This PostgreSQL configuration creates:

  • A database cluster named example-atuin with 2 instances for high availability
  • A 5GB volume for data storage
  • A dedicated atuin user with superuser and database creation privileges
  • A database named atuin owned by the atuin user
  • PostgreSQL version 16

Atuin Server Deployment

The main application deployment handles the Atuin server process with proper security configurations and database connectivity:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: atuin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: atuin
      environment: production
  template:
    metadata:
      labels:
        app: atuin
        environment: production
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: atuin
          image: ghcr.io/atuinsh/atuin:latest
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            runAsNonRoot: true
            runAsUser: 1000
            seccompProfile:
              type: RuntimeDefault
          command:
            - "/bin/sh"
            - "-c"
            - "ATUIN_DB_URI=postgres://$PGUSER:$PGPASSWORD@$PGHOST:$PGPORT/$PGDATABASE exec atuin server start"
          env:
            - name: ATUIN_HOST
              value: 0.0.0.0
            - name: ATUIN_PORT
              value: "8888"
            - name: ATUIN_OPEN_REGISTRATION
              value: "false"
            - name: PGUSER
              valueFrom:
                secretKeyRef:
                  name: atuin.example-atuin.credentials.postgresql.acid.zalan.do
                  key: username
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  name: atuin.example-atuin.credentials.postgresql.acid.zalan.do
                  key: password
            - name: PGHOST
              value: "example-atuin"
            - name: PGPORT
              value: "5432"
            - name: PGDATABASE
              value: "atuin"
            - name: RUST_LOG
              value: "info"
          ports:
            - containerPort: 8888
          resources:
            limits:
              cpu: 250m
              memory: 256Mi
            requests:
              cpu: 50m
              memory: 128Mi

This deployment configuration includes several important features:

Security Hardening:

  • Runs as non-root user (UID 1000) with dropped capabilities
  • Uses seccomp profile for syscall filtering
  • Prevents privilege escalation

Database Integration:

  • Dynamically reads PostgreSQL credentials from the operator-generated secret
  • Constructs the database URI using environment variables for secure connection
  • Uses the PostgreSQL database created by the operator

Resource Management:

  • Sets CPU and memory limits (250m CPU, 256Mi memory)
  • Ensures predictable resource allocation

Network Configuration:

  • Exposes the server on port 8888
  • Binds to all interfaces (0.0.0.0) for cluster access
  • Disables open registration for security

Service Configuration

The Service provides network access to the Atuin server within the cluster and externally:

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: atuin
    environment: production
  name: atuin
spec:
  type: ClusterIP
  ports:
    - name: "8888"
      port: 8888
      targetPort: 8888
  selector:
    app: atuin
    environment: production

This service configuration:

  • Uses ClusterIP type for internal cluster access
  • Exposes the Atuin server on port 8888
  • Routes traffic to pods with the app: atuin, environment: production labels

For external access, consider these alternatives:

  • Ingress (recommended): Use an ingress controller with TLS termination
  • LoadBalancer: If your cluster supports external load balancers
  • Port forwarding: For testing/development: kubectl port-forward svc/atuin 8888:8888

Deployment Steps

To deploy this setup:

  1. Apply the PostgreSQL resource first and wait for it to be ready:

    kubectl apply -f postgresql.yaml
    # Wait for the cluster to be ready (this may take several minutes)
    kubectl get postgresql example-atuin -w
  2. Apply the Deployment configuration:

    kubectl apply -f deployment.yaml
  3. Finally, create the Service:

    kubectl apply -f service.yaml

The PostgreSQL Operator will automatically create the necessary secrets (with names like atuin.example-atuin.credentials.postgresql.acid.zalan.do) and services for database connectivity. The Atuin server will connect to the PostgreSQL database and be accessible within the cluster.

External Access Options

For production deployments, set up proper external access:

Option 1: Ingress (Recommended)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: atuin-ingress
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/ssl-protocols: "TLSv1.2 TLSv1.3"
    # Increase timeouts for potential long-running operations
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - atuin.example.com
      secretName: atuin-tls
  rules:
    - host: atuin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: atuin
                port:
                  number: 8888

This ingress configuration provides:

  • TLS termination with automatic certificate management via cert-manager
  • SSL security with modern TLS protocols and forced HTTPS redirect
  • DNS-based routing to your Atuin server at atuin.example.com
  • Nginx ingress controller compatibility with proper annotations
  • Extended timeouts for potentially long-running Atuin operations

Option 2: Port Forwarding (Development)

kubectl port-forward svc/atuin 8888:8888
# Then access Atuin at http://localhost:8888

Client Configuration

Once external access is configured, you can set up your Atuin clients to sync with this server:

# Configure Atuin client to use your server
atuin login -u <username>
atuin sync

Important: Make sure to create user accounts on the server first, since open registration is disabled for security reasons:

# Connect to the Atuin pod to create users
kubectl exec -it deployment/atuin -- atuin account register -u <username> -e <email>