Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Development/GettingStarted] Docker compose workflow #738

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/web_app_skeleton/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
version: '3.8'
services:
lucky:
build:
context: .
dockerfile: docker/development.dockerfile
environment:
DATABASE_URL: postgres://lucky:password@postgres:5432/lucky
volumes:
- type: bind
source: .
target: /app
- type: volume
source: node_modules
target: /app/node_modules
- type: volume
source: shards_lib
target: /app/lib
depends_on:
- postgres
ports:
- 5001:5001

entrypoint: ["docker/dev_entrypoint.sh"]

postgres:
image: postgres:14-alpine
environment:
POSTGRES_USER: lucky
POSTGRES_PASSWORD: password
volumes:
- type: volume
source: postgres_data
target: /var/lib/postgresql
ports:
# The postgres database container is exposed on the host at port 6543 to
# allow connecting directly to it with postgres clients. The port differs
# from the postgres default to avoid conflict with existing postgres
# servers. Connect to a running postgres container with:
# postgres://lucky:password@localhost:6543/lucky
- 6543:5432

volumes:
postgres_data:
node_modules:
shards_lib:
53 changes: 53 additions & 0 deletions src/web_app_skeleton/docker/dev_entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash

set -euo pipefail

# This is the entrypoint script used for development docker workflows. By
# default it will:
# - Install shards.
# - Run migrations.
# - Start the dev server.
# It also accepts any commands to be run instead.


warnfail () {
echo "$@" >&2
exit 1
}

case ${1:-} in
"") # If no arguments are provided, start lucky dev server.
;;

*) # If any arguments are provided, execute them instead.
exec "$@"
esac

if ! [ -d bin ] ; then
echo "Creating bin directory"
mkdir bin
robacarp marked this conversation as resolved.
Show resolved Hide resolved
fi

echo "Installing npm packages..."
yarn install

if ! shards check ; then
echo "Installing shards..."
shards install
fi

echo "Waiting for postgres to be available..."
./docker/wait-for-it.sh -q postgres:5432

if ! psql -d "$DATABASE_URL" -c '\d migrations' > /dev/null ; then
echo "Finishing database setup..."
lucky db.migrate
fi

if [ -S .overmind.sock ] ; then
echo "Removing old overmind socket file..."
rm .overmind.sock
fi

echo "Starting lucky dev server..."
exec lucky dev
36 changes: 36 additions & 0 deletions src/web_app_skeleton/docker/development.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
FROM crystallang/crystal:1.1.1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be updated, but later versions of the crystal dockerfile don't play well with my computer yet. If someone is willing to test this on an AMD64 architecture with this line updated to 1.3.2, I'll be happy to update it.


# Add the nodesource ppa to apt. Update this to change the nodejs version.
RUN wget https://deb.nodesource.com/setup_16.x -O- | bash

# Apt installs:
# - nodejs (from above ppa) is required for front-end apps.
# - Postgres cli tools are required for lucky-cli.
# - tmux is required for the Overmind process manager.
RUN apt-get update && \
apt-get install -y nodejs postgresql-client tmux && \
rm -rf /var/lib/apt/lists/*

# NPM global installs:
# - Yarn is the default package manager for the node component of a lucky
# browser app.
# - Mix is the default asset compiler.
RUN npm install -g yarn mix

# Installs overmind, not needed if nox is the process manager.
RUN wget https://github.com/DarthSim/overmind/releases/download/v2.2.2/overmind-v2.2.2-linux-amd64.gz && \
gunzip overmind-v2.2.2-linux-amd64.gz && \
mv overmind-v2.2.2-linux-amd64 /usr/bin/overmind && \
chmod +x /usr/bin/overmind
Comment on lines +21 to +24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be using Nox for this? It comes with Lucky CLI which gets installed in the next run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. This provides overmind in the system, but if lucky-cli doesn't need it, it won't hurt anything. I needed this because I developed it against lucky-cli release rather than main.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I forgot Nox integration hasn't been released yet. Yeah, the next release won't need a process runner!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this will ship with Nox, when the cli is revised, I'm happy to pull it now or leave it in. I don't think it hurts and it might help someone get a compose going for an app that doesn't yet have the newest version of lucky on their system.


# Install lucky cli, TODO: fetch current lucky version from source code.
WORKDIR /lucky/cli
RUN git clone https://github.com/luckyframework/lucky_cli . && \
git checkout v0.29.0 && \
shards build --without-development && \
cp bin/lucky /usr/bin

WORKDIR /app
ENV DATABASE_URL=postgres://postgres:[email protected]:5432/postgres
EXPOSE 5001

189 changes: 189 additions & 0 deletions src/web_app_skeleton/docker/wait-for-it.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#!/usr/bin/bash
#
# Pulled from https://github.com/vishnubob/wait-for-it on 2022-02-28.
# Licensed under the MIT license as of 81b1373f.
#
# Below this line, wait-for-it is the original work of the author.
#
# Use this script to test if a given TCP host/port are available

WAITFORIT_cmdname=${0##*/}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waiting


echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }

usage()
{
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}

wait_for()
{
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
else
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
fi
WAITFORIT_start_ts=$(date +%s)
while :
do
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
WAITFORIT_result=$?
else
(echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
WAITFORIT_result=$?
fi
if [[ $WAITFORIT_result -eq 0 ]]; then
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
break
fi
sleep 1
done
return $WAITFORIT_result
}

wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
else
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
fi
WAITFORIT_PID=$!
trap "kill -INT -$WAITFORIT_PID" INT
wait $WAITFORIT_PID
WAITFORIT_RESULT=$?
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
fi
return $WAITFORIT_RESULT
}

# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
WAITFORIT_hostport=(${1//:/ })
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
shift 1
;;
--child)
WAITFORIT_CHILD=1
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-h)
WAITFORIT_HOST="$2"
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
WAITFORIT_HOST="${1#*=}"
shift 1
;;
-p)
WAITFORIT_PORT="$2"
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
WAITFORIT_PORT="${1#*=}"
shift 1
;;
-t)
WAITFORIT_TIMEOUT="$2"
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done

if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi

WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}

# Check to see if timeout is from busybox?
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)

WAITFORIT_BUSYTIMEFLAG=""
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
# Check if busybox timeout uses -t flag
# (recent Alpine versions don't support -t anymore)
if timeout &>/dev/stdout | grep -q -e '-t '; then
WAITFORIT_BUSYTIMEFLAG="-t"
fi
else
WAITFORIT_ISBUSY=0
fi

if [[ $WAITFORIT_CHILD -gt 0 ]]; then
wait_for
WAITFORIT_RESULT=$?
exit $WAITFORIT_RESULT
else
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
wait_for_wrapper
WAITFORIT_RESULT=$?
else
wait_for
WAITFORIT_RESULT=$?
fi
fi

if [[ $WAITFORIT_CLI != "" ]]; then
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
exit $WAITFORIT_RESULT
fi
exec "${WAITFORIT_CLI[@]}"
else
exit $WAITFORIT_RESULT
fi