Skip to content

Instantly share code, notes, and snippets.

@lynsei
Last active October 8, 2021 10:55
Show Gist options
  • Save lynsei/5c58c875d7cde0fe807e06fd7079c691 to your computer and use it in GitHub Desktop.
Save lynsei/5c58c875d7cde0fe807e06fd7079c691 to your computer and use it in GitHub Desktop.
dogi-deprecated.sh
#!/bin/sh
VERSION="0.3.0"
HELP="
dogi
A simple way to deploy your pet projects using Docker, Git, Traefik and SSH.
dogi create # Create a new application
dogi deploy # Deploy an application
dogi env # View or edit environment variables for an application
dogi logs # Show and follow the logs for an application
dogi remove # Remove an existing application
dogi setup # Set up dogi (only useful if you're performing a manual upgrade)
dogi version # Print the version
dogi help # Show this help message
"
HELP_CREATE="
dogi create
Creates a new application.
Usage:
dogi create -n [APP NAME] -d [APP DOMAIN]
Usage over SSH:
ssh -t user@server dogi create -n [APP NAME] -d [APP DOMAIN]
Options:
-n # The application name (required)
-d # The domains, separated by a space, which will be used to serve the application
-o # Additional options to pass to 'docker run' (defaults to '--net=host')
-f # Don't prompt for confirmation and use default directories
-h # Show this message
"
HELP_DEPLOY="
dogi deploy
Deploys an application without code changes. If there have been code changes since the previous
release, use the 'git push' deployment method to include them.
Usage:
dogi deploy -n [APP NAME]
Usage over SSH:
ssh -t user@server dogi deploy -n [APP NAME]
Options:
-n # The application name (required)
-h # Show this message
"
HELP_ENV="
dogi env
View or edit environment variables for an application.
Usage:
dogi env -n [APP NAME]
Usage over SSH:
ssh -t user@server dogi env -n [APP NAME]
Options:
-n # The application name (required)
-h # Show this message
"
HELP_LOGS="
dogi logs
Show and follow the logs for an application.
Usage:
dogi logs -n [APP NAME]
Usage over SSH:
ssh -t user@server dogi logs -n [APP NAME]
Options:
-n # The application name (required)
-h # Show this message
"
HELP_REMOVE="
dogi remove
Removes an application entirely.
Usage:
dogi remove -n [APP NAME]
Usage over SSH:
ssh -t user@server dogi remove -n [APP NAME]
Options:
-n # The application name (required)
-h # Show this message
"
DEPLOY_SCRIPT='#!/bin/sh
set -eu
APPS_DIR="%s"
APP_NAME="%s"
APP_DOMAIN="%s"
DOCKER_OPTS="%s"
echo "### Checking out the code..."
cd $APPS_DIR/$APP_NAME/git
GIT_WORK_TREE=$APPS_DIR/$APP_NAME/app git checkout -f
echo "### Building the image..."
docker build -t $APP_NAME $APPS_DIR/$APP_NAME/app
echo "### Stopping the previously running instance..."
docker stop $APP_NAME 2>&1 | xargs docker rm > /dev/null 2>&1 || echo "### Nothing to stop: no previous instance was running"
echo "### Starting the application..."
docker run --name $APP_NAME \
-d --restart unless-stopped \
--env-file $APPS_DIR/$APP_NAME/env \
--network web \
-l "traefik.backend=$APP_NAME" \
-l "traefik.frontend.rule=Host:$APP_DOMAIN" \
$DOCKER_OPTS \
$APP_NAME
echo "### Cleaning up unused containers and images..."
docker container prune -f --filter "until=168h"
docker image prune -a -f --filter "until=168h"
echo "### Your app $APP_NAME was deployed successfully."
'
TRAEFIK_CONFIG='
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[acme]
email = "%s"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
[docker]
watch = true
network = "web"
'
TRAEFIK_IMAGE="traefik:1.7.28-alpine"
DEFAULT_TRAEFIK_DIR="$HOME/.traefik"
APPS_DIR="$HOME/apps"
DOCKER_OPTS=""
FORCE=false
setup(){
if ! command -v docker > /dev/null; then
echo "### You need to install Docker on the host machine before proceeding."
exit 1
fi
if ! command -v git > /dev/null; then
echo "### You need to install git on the host machine before proceeding."
exit 1
fi
if id -nG "$USER" | grep -vqwE "docker|root"; then
echo "### The user '$USER' does not belong to the 'docker' group. Run 'sudo usermod -a -G docker $USER' to fix this."
exit 1
fi
if test -z "$(docker network ls -qf name=web)"; then
echo "### Setting up docker web network..."
docker network create -d bridge --subnet 192.168.0.0/24 --gateway 192.168.0.1 web
fi
if test -z "$(docker ps -qf name=traefik)"; then
echo "### Starting traefik..."
[ "$FORCE" = true ] || printf "### Where should Traefik store data and configuration? ($DEFAULT_TRAEFIK_DIR) "; read -r TRAEFIK_DIR
[ -z "$TRAEFIK_DIR" ] && TRAEFIK_DIR=$DEFAULT_TRAEFIK_DIR
mkdir -p "$TRAEFIK_DIR"
touch "$TRAEFIK_DIR/acme.json"
chmod 600 "$TRAEFIK_DIR/acme.json"
[ "$FORCE" = true ] || printf "### Which email should be associated with Let's Encrypt SSL certificates? "; read -r EMAIL
[ -z "$EMAIL" ] && EMAIL=""
printf "$TRAEFIK_CONFIG" "$EMAIL" > "$TRAEFIK_DIR/traefik.toml"
docker run --name traefik \
-d --restart unless-stopped \
--network web --cap-add=cap_net_bind_service \
-p 80:80 \
-p 443:443 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$TRAEFIK_DIR/traefik.toml:/traefik.toml" \
-v "$TRAEFIK_DIR/acme.json:/acme.json" \
$TRAEFIK_IMAGE
# Give traefik some time to resume before we apply the default configuration in the next step
sleep 10
fi
}
create(){
[ "$HELP_REQUESTED" = true ] || [ -z "$APP_NAME" ] && { echo "$HELP_CREATE"; exit 1; }
[ -z "$APP_DOMAIN" ] && { echo "$HELP_CREATE"; exit 1; }
setup
echo "### Creating application '$APP_NAME' with domain '$APP_DOMAIN'"
[ "$FORCE" = true ] || { printf "### Are you sure? (y/n) "; read -r CONFIRM; [ "$CONFIRM" != "y" ] && exit 1; }
echo "### Setting up directories and files..."
mkdir -p "$APPS_DIR/$APP_NAME/git" "$APPS_DIR/$APP_NAME/app"
[ ! -f "$APPS_DIR/$APP_NAME/env" ] && echo "# Place your environment variables here" > "$APPS_DIR/$APP_NAME/env"
printf "$DEPLOY_SCRIPT" "$APPS_DIR" "$APP_NAME" "$APP_DOMAIN" "$DOCKER_OPTS" > "$APPS_DIR/$APP_NAME/deploy"
chmod +x "$APPS_DIR/$APP_NAME/deploy"
echo "### Setting up git repository..."
[ ! -f "$APPS_DIR/$APP_NAME/git/HEAD" ] && git init --bare "$APPS_DIR/$APP_NAME/git" > /dev/null
ln -sf "$APPS_DIR/$APP_NAME/deploy" "$APPS_DIR/$APP_NAME/git/hooks/post-update"
echo "### Application created successfully."
SSH_COMMAND="[SSH USER]@[SSH SERVER]"
SSH_SERVER=$(echo "$SSH_CONNECTION" | cut -d' ' -f3)
[ -n "$SSH_SERVER" ] && SSH_COMMAND="$USER@$SSH_SERVER"
echo "### Run 'git remote add dogi $SSH_COMMAND:$APPS_DIR/$APP_NAME/git' to connect your local git repository to dogi."
}
deploy(){
[ "$HELP_REQUESTED" = true ] || [ -z "$APP_NAME" ] && { echo "$HELP_DEPLOY"; exit 1; }
"$APPS_DIR/$APP_NAME/deploy"
}
env(){
[ "$HELP_REQUESTED" = true ] || [ -z "$APP_NAME" ] && { echo "$HELP_ENV"; exit 1; }
edit "$APPS_DIR/$APP_NAME/env"
}
logs(){
[ "$HELP_REQUESTED" = true ] || [ -z "$APP_NAME" ] && { echo "$HELP_LOGS"; exit 1; }
docker logs -f "$APP_NAME"
}
remove(){
[ "$HELP_REQUESTED" = true ] || [ -z "$APP_NAME" ] && { echo "$HELP_REMOVE"; exit 1; }
echo "### This will remove application '$APP_NAME' entirely, including the remote Git repository"
[ "$FORCE" = true ] || { printf "### Are you sure? (y/n) "; read -r CONFIRM; [ "$CONFIRM" != "y" ] && exit 1; }
echo "### Stopping the previously running instance..."
docker stop "$APP_NAME" 2>&1 | xargs docker rm > /dev/null 2>&1 || echo "### Nothing to stop: no previous instance was running"
echo "### Removing directories and files..."
rm -rf "${APPS_DIR:?}/${APP_NAME:?}"
}
ACTION=$1 && [ $# -ne 0 ] && shift
while getopts "n:d:p:o:fh" OPTION
do
case $OPTION in
n)
APP_NAME=$OPTARG
echo "$APP_NAME" | grep -vqE "^[a-zA-Z0-9_]+$" && { echo "### The name argument may only contain letters, numbers or underscore"; exit 1; }
;;
d)
APP_DOMAIN=$OPTARG
;;
o)
DOCKER_OPTS=$OPTARG
;;
f)
FORCE=true
;;
h | *)
HELP_REQUESTED=true
;;
esac
done
case "$ACTION" in
setup)
setup;;
create)
create;;
deploy)
deploy;;
env)
env;;
logs)
logs;;
remove)
remove;;
version)
echo "$VERSION"; exit 0;;
help | *)
echo "$HELP"; exit
1;;
esac

Dogi helps you deploy your pet projects using Docker, Git, Traefik and SSH.

Get the code at https://github.com/lipanski/dogi.

Goals:

  • Keep things simple.
  • Deploy your application with Git pushes (Heroku-style).
  • Use Docker and Dockerfiles to define and contain your application dependencies.
  • Expose and manage a web server in the most simple way using Traefik.
  • Manage SSL certificates automatically with Traefik and Let's Encrypt.
  • Recover from outages or server restarts without any manual intervention.
  • Pass commands through SSH to manage your applications.
  • Handle all this with a simple, POSIX-compliant and easy to review Shell script.

Non-goals:

  • Handling more than one server at a time.
  • Handling complex use cases.
  • Anything anywhere close to Kubernetes, Docker Swarm, build packs or Helm charts.
  • Using Docker to establish network isolation (all containers are part of the same internal network).

Quick start

Let's say you've built a web app to showcase your awesome coin collection. You've added a Dockerfile which exposes a port via EXPOSE. You'd like to see it running at https://coins.example.com.

The following instructions assume that you've installed Dogi, you can connect to your server by calling ssh [email protected] and you've pointed your domain's DNS records to this machine.

Let's create the app:

ssh -t [email protected] dogi create -n coins -d coins.example.com

Some Dogi commands are interactive so we need to call ssh with the -t (request TTY) option.

Next, let's configure the app with some environment variables:

ssh -t [email protected] dogi env -n coins

Connect your application's local Git repository to the newly created remote Git repository:

git remote add dogi [email protected]:/home/dogi/apps/coins/git

...and trigger your first deployment by making a push to the remote repository:

git push dogi master

If the deployment was successful and the domain's DNS records are pointing to your server, your website should be available under https://coins.example.com.

If you'd like to trigger a deployment without making a Git push (without triggering a code change), you can call:

ssh -t [email protected] dogi deploy -n coins

You can stream your application's logs by calling:

ssh -t [email protected] dogi logs -n coins

Finally, if you'd like to remove the application entirely run:

ssh -t [email protected] dogi remove -n coins

Installation

Dogi is basically just a POSIX-compliant Shell script with Docker and git as dependencies. It has been tested on Ubuntu 18.04 but it should run fine on any recent Ubuntu or Debian version.

Here's how to set it up on Ubuntu:

  1. Install git:

    sudo apt-get install git
  2. Install Docker. Please refer to the official installation guide or try your luck with:

    sudo apt-get install docker.io
  3. Install Dogi:

    curl -s https://raw.githubusercontent.com/lipanski/dogi/master/dogi > /usr/local/bin/dogi
    chmod +x /usr/local/bin/dogi

    If you prefer to use a specific version of Dogi, replace the branch name (master) in the URL above with a tagged version.

  4. (Optional, but recommended) Create a dedicated user for running Dogi:

    adduser --disabled-password --shell /bin/bash dogi

    All relevant files will be placed inside $HOME/apps so ensure the user has a home directory and it uses the right permissions.

  5. Make sure the user running Dogi belongs to the docker group:

    addgroup docker || true
    usermod -aG docker dogi
  6. Set up your public SSH key under $HOME/.ssh/authorized_keys so that you can access the server and allow pushes to the Dogi git repositories.

  7. That's it! Assuming you can reach the server by calling ssh [email protected], let's test the installation:

    ssh -t [email protected] dogi help

    ...which should print the Dogi help message.

  8. (Optional) Simplify the way you access Dogi over SSH by adding a shortcut to your ~/.ssh/config:

    Host my-server
      HostName 1.2.3.4
      User dogi
      RequestTTY force
    

    Now you can call any Dogi command like this:

    ssh my-server dogi help

    Bear in mind that scp doesn't play nicely with RequestTTY force so avoid using scp over such a shortcut or remove the setting in favour of explicitly adding -t to every Dogi SSH call.

Usage

Run dogi help for a list of all available commands and options.

Development

Run shellcheck (0.7+):

shellcheck dogi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment