Setting up Atuin server on K8S
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:
- A running Kubernetes cluster with admin access
kubectlconfigured to access your cluster- 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.yamlAlternatively, use kustomization (requires kubectl 1.14+):
kubectl apply -k github.com/zalando/postgres-operator/manifestsFor 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:
- PostgreSQL Database - Using the PostgreSQL Operator for managed database
- Deployment - The main Atuin server application
- Service - Network access to the Atuin server
- 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-atuinwith 2 instances for high availability - A 5GB volume for data storage
- A dedicated
atuinuser with superuser and database creation privileges - A database named
atuinowned by theatuinuser - 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: 128MiThis 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: productionThis service configuration:
- Uses
ClusterIPtype for internal cluster access - Exposes the Atuin server on port 8888
- Routes traffic to pods with the
app: atuin, environment: productionlabels
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:
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 -wApply the Deployment configuration:
kubectl apply -f deployment.yamlFinally, 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: 8888This 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:8888Client 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 syncImportant: 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>