Hot questions for Using Vapor in docker

Question:

The Config/server.json file doesn't seem to be read by Vapor 3, and as such I can't configure the hostname and port that a Vapor 3 app binds to.

Does Vapor 3 have a different method for this?


Answer:

You can use NIOServerConfig.

let serverConfiure = NIOServerConfig.default(hostname: "0.0.0.0", port: 9090)
services.register(serverConfiure)

Vapor version is 3.0.3

Question:

Currently building a package to test some devOps configurations with AWS. Building an application with Swift Vapor3, PostgreSQL 11, Docker. Given my github Repo the project builds/tests/runs just fine with vapor build vapor test vapor run given that you have a local installation of postgresql installed with a username: test, password: test

However my api is not connecting to my DB and am worried my configuration is wrong.

version: "3.5"
services:
  api:
    container_name: vapor_it_container
    build:
      context: .
      dockerfile: web.Dockerfile
    image: api:dev
    networks:
      - vapor-it
    environment:
      POSTGRES_PASSWORD: 'test'
      POSTGRES_DB: 'test'
      POSTGRES_USER: 'test'
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
    ports:
      - 8080:8080
    volumes:
      - .:/app
    working_dir: /app
    stdin_open: true
    tty: true
    entrypoint: bash
    restart: always
    depends_on:
      - db

  db:
    container_name: postgres_container
    image: postgres:11.2-alpine
    restart: unless-stopped
    networks:
      - vapor-it
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      PGDATA: /var/lib/postgresql/data
    volumes:
      - database_data:/var/lib/postgresql/data

  pgadmin:
    container_name: pgadmin_container
    image: dpage/pgadmin4
    environment:
      PGADMIN_DEFAULT_EMAIL: test@test.com
      PGADMIN_DEFAULT_PASSWORD: admin
    volumes:
      - pgadmin:/root/.pgadmin
    ports:
      - "${PGADMIN_PORT:-5050}:80"
    networks:
      - vapor-it
    restart: unless-stopped

networks:
  vapor-it:
    driver: bridge

volumes:
  database_data:
  pgadmin:
  #  driver: local

Also while reading the Docker postgres docs I came across this in the "Caveats" section.

If there is no database when postgres starts in a container, then postgres will create the default database for you. While this is the expected behavior of postgres, this means that it will not accept incoming connections during that time. This may cause issues when using automation tools, such as docker-compose, that start several containers simultaneously.postgres dockerhub

I have not made those changes because I am not sure how to go about making that file or how the configuration would look. Has anyone done something like this that has some experience with connecting to Postgresql and using vapor as a back end?


Answer:

The theory is, a well-behaved container should be able to gracefully handle not having its dependencies running, because despite the best efforts of your container scheduler, containers may come and go. So if your app needs a DB, but at any given moment the DB is unavailable, it should respond rationally. For example, returning a 503 for an HTTP request, or trying again after a delay for a scheduled task.

That’s theory though, and not always applicable. In your situation, maybe you really do just need your Vapor app to wait for Postgres to come available, in which case you could use a wrapper script that polls your DB and only starts your main app after the DB is ready.

See this suggested wrapper script from the Docker docs:

#!/bin/sh
# wait-for-postgres.sh

set -e

host="$1"
shift
cmd="$@"

until PGPASSWORD=$POSTGRES_PASSWORD psql -h "$host" -U "postgres" -c '\q'; do
  >&2 echo "Postgres is unavailable - sleeping"
  sleep 1
done

>&2 echo "Postgres is up - executing command"
exec $cmd
command: ["./wait-for-postgres.sh", "db", "vapor-app", "run"]

Question:

I use docker to compose Vapor, PostgreSQL and Nginx for a project, my docker-compose.yml like this:

version: "3.6"

services:
    vapor:
        build:
            context: ./vapor
        image: ${CURRENT_VAPOR_IMG}
        ports:
            - 8080:8080
        volumes:
            - ${HOST_ROOT}:${CONTAINER_ROOT}
        working_dir: ${CONTAINER_ROOT}
        tty: true
        entrypoint: bash
        networks:
            - x-net

    nginx:
        build:
            context: ./nginx
        image: ${CURRENT_NGINX_IMG}
        ports:
            - ${HOST_HTTP_PORT}:80
        volumes:
            - ${HOST_ROOT}:${CONTAINER_ROOT}
        networks:
            - x-net

    psql:
        image: ${CURRENT_DB_IMG}
        ports:
            - 5432:5432
        environment:
            - POSTGRES_DB=xxx
            - POSTGRES_USER=xxx
            - POSTGRES_PASSWORD=pass
        volumes:
            - ~/x/x-db:/var/lib/postgresql/data
    networks:
        - x-net

networks:
    x-net:
         driver: bridge

After I start all the container by running docker-compose up, then enter to vapor's container to build && run the project, it will prompt an error to the console:

NIO.ChannelError.connectFailed(NIO.NIOConnectionError(host: "localhost", port: 5432, dnsAError: nil, dnsAAAAError: nil, connectionErrors: [NIO.SingleConnectionFailure(target: [IPv6]localhost/::1:5432, error: connection reset (error set): Connection refused (errno: 61)), NIO.SingleConnectionFailure(target: [IPv4]localhost/127.0.0.1:5432, error: connection reset (error set): Connection refused (errno: 61))]))

Then I run the vapor project on local machine and keep the psql container running, it works normally, such as finished the first migration with models.

Is there any mistakes on my configuration of docker or any others?


Answer:

To connect to database inside container dont use localhost as a db host but your db container name. So in your case host is psql. Here your docker compose is not well formatted psql and nginx must have one tab more. But maybe its just SO formatting wrong.

Question:

i have one weird problem. I'm running vapor inside docker on a digital ocean machine. All my requests are working as expected except of PATCH and DELETE requests.

In the following code patch enter will be logged but not parameter decode. The HTTP request appears to run forever and other requests in parallel are not possible

func patch(_ req: Request) throws -> Future<Manufacturer> {
    let logger = try req.make(Logger.self)
    logger.info("patch enter")
    return try req.parameters.next(Manufacturer.self).flatMap { manufacturer in
        logger.info("parameter decode")
        return try req.content.decode(Manufacturer.self).flatMap { patchManufacturer in
            logger.info("content decode")
            manufacturer.name = patchManufacturer.name
            return manufacturer.save(on: req)
        }
    }
}

Every other method with req.parameters.next(ModelName.self).flatMap is also not working inside my digital ocean docker machine.

Something like a simple create works:

func create(_ req: Request) throws -> Future<Manufacturer> {
    return try req.content.decode(Manufacturer.self).flatMap { manufacturer in
        return manufacturer.save(on: req)
    }
}

So i thought there must be something wrong with:

return try req.parameters.next(Manufacturer.self).flatMap { manufacturer in

The weird part is: when i start the same docker compose configuration on my mac, everything works as expected - no hung up http request.

I dont know what to do to find the mistake, can somebody help me? If there is some file which i should provide to solve the problem, please leave a short note and i will update the post.

Thanks!


Answer:

I got the solution from the awesome guys in the Vapor Discord Chat: https://discord.gg/BnXmVGA

Thanks to @vzsg and @jimmya92.

The vapor app is running on the smallest digital ocean droplet:

  • 1GB memory
  • 1vCPU
  • 25GB SSD

there's a known issue that Fluent's default configuration limits the number of connections too much on single core systems which can lead to deadlocks like this drop this in configure.swift to work around it:

let poolConfig = DatabaseConnectionPoolConfig(maxConnections: 16)
services.register(poolConfig)