Back to Blog
Lesson 34 of the CI/CD: Continuous Integration from Scratch course
DevOpsAugust 9, 20264 min read

Automated Deployment Scripts: Mastering Remote Server Provisioning

Learn to write robust, automated deployment scripts for your cloud infrastructure. Master idempotency, remote execution, and local testing for reliable CI/CD.

devopsdeploymentautomationbashsshci-cd
A close-up view of PHP code displayed on a computer screen, highlighting programming and development concepts.

Previously in this course, we covered Staging Environments: Mastering Deployment Targets in CI/CD, where we defined the "where" of our deployment. Today, we focus on the "how" by writing automated deployment scripts that handle the heavy lifting of moving our application code to a remote server.

Why You Need Deployment Scripts

In a professional DevOps workflow, you should never manually SSH into a server to copy files or restart services. Manual steps are prone to human error and create "snowflake servers"—environments that are unique and impossible to replicate.

By codifying your deployment process into a script, you achieve two goals:

  1. Repeatability: The same script produces the same outcome every time.
  2. Automation: Your CI/CD pipeline can trigger the script without human intervention.

First Principles: The Idempotent Script

A robust deployment script must be idempotent. This means you can run the script multiple times without changing the result beyond the initial application. If the directory already exists, don't crash; if the service is already running, just restart it.

Designing Your Script

For our current project, we will use a Bash script that performs three core actions:

  1. Sync: Copies files from the CI runner to the target server.
  2. Configure: Sets up the environment (permissions, dependencies).
  3. Restart: Bounces the application service.

Worked Example: A Simple Deployer

Create a file named deploy.sh in your project root. We’ll use rsync for the transfer, as it is efficient and handles file deltas perfectly.

Bash
#!/bin/bash

# Configuration
SERVER_USER="deploy"
SERVER_IP="192.168.1.100"
DEPLOY_DIR="/var/www/myapp"

echo "Starting deployment to $SERVER_IP..."

# 1. Sync files using rsync
# -a: archive mode, -v: verbose, -z: compress
rsync -avz --exclude='.git' ./ $SERVER_USER@$SERVER_IP:$DEPLOY_DIR

# 2. Execute remote command to restart the service
ssh $SERVER_USER@$SERVER_IP << 'EOF'
  cd /var/www/myapp
  # Ensure dependencies are updated (idempotent)
  npm install --production
  # Restart the systemd service
  sudo systemctl restart myapp.service
EOF

echo "Deployment complete!"

To make this script executable on your machine, run chmod +x deploy.sh. This follows the patterns discussed in Introduction to CLI Tooling: Making Node.js Scripts Executable.

Testing the Script Locally

Before wiring this into GitHub Actions, you must verify it works from your terminal.

  1. Connectivity: Ensure you have SSH key access to your target server (do not use passwords, as scripts cannot prompt for them).
  2. Dry Run: Change the rsync command to include the --dry-run flag. This will show you exactly which files would be moved without actually touching the remote filesystem.
  3. Execution: Run ./deploy.sh and watch the output. If it fails, check the logs—usually, it’s a permission issue on the remote server or a missing SSH key.

Hands-on Exercise

  1. Create your script: Based on the example above, customize the SERVER_IP and DEPLOY_DIR variables to point to your test server.
  2. Add a health check: Add a line at the end of your ssh block (e.g., curl -f http://localhost:3000) to confirm the app is actually running after the restart.
  3. Execute: Run the script and confirm that no errors are returned.

Common Pitfalls

  • Hardcoding Passwords: Never put passwords in your scripts. Use SSH keys. If you need to authenticate, use ssh-agent or GitHub Secrets later in the course.
  • Assuming Absolute Paths: Always use absolute paths in your cd commands on the remote server. Relative paths can lead to deleting the wrong files if the cd command fails.
  • The "Half-Deployed" State: If a command fails halfway through, your app might be broken. Always try to keep your critical operations atomic, or add logic to roll back if a step fails.

FAQ

Q: Why use rsync instead of git pull? A: rsync is better for deployment because it handles file deletions and permissions consistently. git pull on a server can lead to conflicts if you ever modify files directly on the server (which you shouldn't).

Q: How do I handle sudo permissions? A: If you need sudo for systemctl, ensure the deploy user has permission in the /etc/sudoers file to run that specific command without a password (using NOPASSWD).

Recap

Automated deployment scripts are the backbone of your delivery pipeline. By focusing on idempotency and using tools like rsync and ssh, you move away from manual "copy-paste" deployments toward a professional, repeatable process. You've verified your logic locally, which means you are ready to integrate this into your CI pipeline.

Up next

In the next lesson, we will transition from running this script manually to automating it within your GitHub Actions workflow by Deploying to Staging.

Similar Posts