Just is a modern command runner that provides a convenient way to save and run project-specific commands. Think of it as a modern, more user-friendly alternative to Makefiles for DevOps task automation. With a simple syntax and powerful features, Justfile helps standardize common operations across your team.
Why Just over Make?
- Simpler syntax: No tabs-vs-spaces issues, no implicit rules
- Better error messages: Clear, helpful error reporting
- Built-in features: Variable interpolation, conditionals, OS detection, dotenv support
- Cross-platform: Works on Linux, macOS, and Windows
- Not a build system: Focused on running commands, not tracking file dependencies
Installation
# Install on Linux
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
# Or via package managers
# Ubuntu/Debian
sudo apt install just
# macOS
brew install just
# Verify
just --version
DevOps Justfile
# justfile — DevOps task runner
# Load environment variables from .env
set dotenv-load
# Default recipe (runs when you type 'just')
default:
@just --list
# ============ Server Management ============
# SSH into a server
ssh server="web1":
ssh deploy@{{server}}.example.com
# Check server status across all hosts
status:
@echo "=== Server Status ==="
ansible all -m shell -a "uptime && df -h / | tail -1" -i inventory/production
# Update packages on all servers
update-packages:
ansible-playbook playbooks/patch-servers.yml -i inventory/production
# Reboot a specific server with confirmation
reboot server:
@echo "About to reboot {{server}}.example.com"
@read -p "Are you sure? (y/N) " confirm && [ "$$confirm" = "y" ] || exit 1
ssh deploy@{{server}}.example.com 'sudo reboot'
# ============ Deployment ============
# Deploy application to production
deploy version="latest":
@echo "Deploying version {{version}} to production..."
ansible-playbook playbooks/deploy.yml \
-i inventory/production \
-e "app_version={{version}}"
# Deploy to staging first
deploy-staging version="latest":
ansible-playbook playbooks/deploy.yml \
-i inventory/staging \
-e "app_version={{version}}"
# Rolling restart of web servers
rolling-restart:
ansible webservers -m systemd -a "name=webapp state=restarted" \
-i inventory/production --forks 1
# ============ Database ============
# Create database backup
db-backup:
#!/usr/bin/env bash
set -euo pipefail
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "Creating backup: db_backup_${TIMESTAMP}.sql.gz"
ssh db1.example.com "pg_dump -U postgres myapp | gzip" > \
backups/db_backup_${TIMESTAMP}.sql.gz
echo "Backup saved: backups/db_backup_${TIMESTAMP}.sql.gz"
# Restore database from backup
db-restore file:
@echo "Restoring from {{file}}"
@read -p "This will OVERWRITE the database. Continue? (y/N) " confirm && [ "$$confirm" = "y" ] || exit 1
zcat {{file}} | ssh db1.example.com "psql -U postgres myapp"
# ============ SSL Certificates ============
# Renew all SSL certificates
ssl-renew:
/opt/scripts/ssl-renew-and-deploy.sh
# Check certificate expiry dates
ssl-check:
/opt/scripts/check-ssl-expiry.sh
# ============ Docker ============
# Build and push Docker image
docker-build tag="latest":
docker build -t myapp:{{tag}} .
docker tag myapp:{{tag}} registry.example.com/myapp:{{tag}}
docker push registry.example.com/myapp:{{tag}}
# Clean up Docker resources
docker-clean:
docker system prune -af --volumes
# ============ Monitoring ============
# View recent logs
logs service="webapp" lines="100":
ssh web1.example.com "journalctl -u {{service}} -n {{lines}} --no-pager"
# Check disk usage across all servers
disk-usage:
ansible all -m shell -a "df -h / | tail -1" -i inventory/production
# ============ Terraform ============
# Plan infrastructure changes
tf-plan env="production":
cd terraform && \
terraform workspace select {{env}} && \
terraform plan -var-file="environments/{{env}}.tfvars"
# Apply infrastructure changes
tf-apply env="production":
cd terraform && \
terraform workspace select {{env}} && \
terraform apply -var-file="environments/{{env}}.tfvars"
Advanced Features
# Conditional logic based on OS
backup_cmd := if os() == "linux" { "tar czf" } else { "zip -r" }
# Set shell
set shell := ["bash", "-euo", "pipefail", "-c"]
# Private recipes (not shown in --list)
[private]
_check-ssh-agent:
@ssh-add -l > /dev/null 2>&1 || (echo "SSH agent not running" && exit 1)
# Recipe dependencies
deploy version="latest": _check-ssh-agent
@echo "Deploying {{version}}..."
# Run recipes from a different directory
[working-directory: 'terraform']
tf-init:
terraform init
# Confirm before running
[confirm("Are you sure you want to deploy to production?")]
deploy-prod:
ansible-playbook deploy.yml -i production
# OS-specific recipes
[linux]
install-deps:
sudo apt install -y ansible terraform
[macos]
install-deps:
brew install ansible terraform
Team Usage
# List all available commands
just --list
# Output:
# Available recipes:
# db-backup # Create database backup
# db-restore file # Restore database from backup
# deploy version # Deploy application to production
# deploy-staging # Deploy to staging first
# disk-usage # Check disk usage across all servers
# docker-build tag # Build and push Docker image
# logs service lines # View recent logs
# ssl-check # Check certificate expiry dates
# ssh server # SSH into a server
# status # Check server status
# tf-plan env # Plan infrastructure changes
# Run with arguments
just deploy v2.1.0
just ssh web2
just logs nginx 500
just tf-plan staging
Best Practices
- Commit your justfile to version control — it documents common operations
- Add descriptions as comments above recipes for
just --listdocumentation - Use confirmation prompts for destructive operations
- Set default values for optional parameters to reduce typing
- Use private recipes (prefix with
_) for helper tasks - Load secrets from .env with
set dotenv-loadinstead of hardcoding