Skip to content

Instantly share code, notes, and snippets.

@tfausak
Created January 12, 2017 22:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tfausak/cdcc2e33c9c418278262650eb96b7b05 to your computer and use it in GitHub Desktop.
Save tfausak/cdcc2e33c9c418278262650eb96b7b05 to your computer and use it in GitHub Desktop.
Haskll & Docker Compose
.dockerignore
.gitignore
.stack-work
docker-compose.yml
Dockerfile
README.markdown
/.stack-work/
/*.cabal

Haskell & Docker Compose

This is a simple example of a Haskell application running in a Docker container with a database connection. In this particular case, the database also runs in a Docker container. However in general the database could live anywhere. For example, you might run the application in Docker but run the database on your host machine.

Just like the Haskell & Docker example, this does not prevent you from running the application like normal. Install Stack and Postgres, then you can get going with:

> stack setup
> stack build
> stack exec counter

And if you want to run the application in Docker but Postgres on your machine, get Docker and do:

> docker build . --tag counter
> docker run \
  --env DATABASE='host=localhost user=postgres' \
  --interactive \
  --publish-all \
  --tty \
  counter

But neither of those approaches are new. You can avoid setting up both Haskell and Postgres on your machine by using Docker Compose. It will build the necessary containers and connect them together. All you need to do is:

> docker-compose build
> docker-compose up -d db # Start the DB first because it needs to initialize.
> docker-compose up -d

Then go to http://localhost:8080. 🎉

The only difference between this example and the last one is the introduction of docker-compose.yml. It tells the docker-compose command how these containers should be built and how they relate to each other.

version: '2'
services:
web:
build: .
ports:
- '8080:80'
volumes:
- .:/project
environment:
- DATABASE=host=db user=postgres
db:
image: 'postgres:9.6'
FROM debian:8.6
# Install dependencies.
RUN apt-get update && \
apt-get install --assume-yes curl gcc libgmp-dev libpq-dev make xz-utils zlib1g-dev
# Install Stack.
RUN curl --location https://www.stackage.org/stack/linux-x86_64-static > stack.tar.gz && \
tar xf stack.tar.gz && \
cp stack-*-linux-x86_64-static/stack /usr/local/bin/stack && \
rm -f -r stack.tar.gz stack-*-linux-x86_64-static/stack && \
stack --version
# Install GHC.
WORKDIR /project
COPY stack.yaml /project
RUN stack setup && \
stack exec -- ghc --version
# Install dependencies.
COPY package.yaml /project
RUN stack build --only-dependencies
# Build project.
COPY . /project
RUN stack build --copy-bins --local-bin-path /usr/local/bin
# Run project.
ENV HOST 0.0.0.0
ENV PORT 80
EXPOSE 80
CMD /usr/local/bin/counter
{-# LANGUAGE OverloadedStrings #-}
import Data.Function ((&))
import Data.Monoid ((<>))
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Lazy.Char8 as BSL8
import qualified Data.Maybe as Maybe
import qualified Data.String as String
import qualified Database.PostgreSQL.Simple as SQL
import qualified Network.HTTP.Types as HTTP
import qualified Network.Wai as Wai
import qualified Network.Wai.Handler.Warp as Warp
import qualified System.Envy as Envy
main :: IO ()
main = do
maybeConfig <- Envy.decode
let config = Maybe.fromMaybe Envy.defConfig maybeConfig
print config
let settings = makeSettings config
connection <- SQL.connectPostgreSQL (configDatabase config)
_ <- SQL.execute_ connection "create table if not exists visits (id serial primary key)"
Warp.runSettings settings (application connection)
data Config = Config
{ configDatabase :: BS8.ByteString
, configHost :: Warp.HostPreference
, configPort :: Warp.Port
} deriving (Eq, Show)
instance Envy.FromEnv Config where
fromEnv = do
database <- Envy.env "DATABASE"
host <- Envy.env "HOST"
port <- Envy.env "PORT"
pure Config
{ configDatabase = database
, configHost = String.fromString host
, configPort = read port
}
instance Envy.DefConfig Config where
defConfig = Config
{ configDatabase = BS8.pack ""
, configHost = String.fromString "127.0.0.1"
, configPort = 8080
}
makeSettings :: Config -> Warp.Settings
makeSettings config =
Warp.defaultSettings & Warp.setHost (configHost config) &
Warp.setPort (configPort config)
application :: SQL.Connection -> Wai.Application
application connection _request respond = do
count <- SQL.withTransaction connection (do
[oldCount] <- SQL.query_ connection "select count(*) from visits"
let newCount = SQL.fromOnly oldCount + 1 :: Int
_ <- SQL.execute_ connection "insert into visits (id) values (default)"
pure newCount)
let status = HTTP.status200
let headers = []
let body = "You are visitor number " <> BSL8.pack (show count) <> ".\n"
respond (Wai.responseLBS status headers body)
name: counter
version: '1.0.0'
executables:
counter:
main: Main.hs
dependencies:
- base
- bytestring
- envy
- http-types
- postgresql-simple
- wai
- warp
ghc-options:
- -Wall
- -threaded
- -rtsopts
- -with-rtsopts=-N
resolver: lts-7.15
extra-deps:
- envy-1.2.0.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment