1
votes

Is there a way to change IP on MySQL using Dockerfile? I have a problem with my app.

I have this configuration:

let ormConfig: any;

if (process.env.NODE_ENV === 'test') {
  // test environment database should be drop every time. so synchronize should be true for this config
  ormConfig = {
    type: 'mysql',
    host: '0.0.0.0',
    // host: 'localhost', // not needed if run on docker
    username: 'root',
    password: 'password',
    database: 'test_db',
    synchronize: true,
    dropSchema: true,
    entities: ['src/models/**/*.ts']
  };
} else {
  ormConfig = {
    type: 'mysql',
    host: 'db', // docker service database name
    // host: 'localhost', // not needed if run on docker
    // port: 3306, // not needed if run on docker
    username: 'root',
    password: 'password',
    database: 'development_db',
    synchronize: false,
    logging: true,
    entities: ['src/models/**/*.ts'],
    migrations: ['db/migrations/**/*.ts'],
    subscribers: ['src/subscribers/**/*.ts'],
    cli: {
      entitiesDir: 'src/models',
      migrationsDir: 'db/migrations',
      subscribersDir: 'src/subscribers'
    }
  };
}

export = ormConfig;

I'm using typeorm for my ORM. On else block I have a host name db this works when connecting to my db if I'm using my application, but when I tried running a migration I have to change the host to 0.0.0.0 and found out that it's because of the docker MySQL container (server_db image):

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
aaaa4d178537        server_server       "docker-entrypoint.s…"   About an hour ago   Up About an hour    0.0.0.0:8000->8000/tcp              server_server_1
e5021687b86b        server_db           "docker-entrypoint.s…"   About an hour ago   Up About an hour    0.0.0.0:3306->3306/tcp, 33060/tcp   server_db_1

I'm just tired of changing and changing it before and after migration.

Any idea just to use localhost? or custom IP like 1.2.3.456 something like that?

Also here's my docker-compose file:

version: "3.3"
services:
  db:
    build: ./db
    restart: always
    env_file:
      - .env
    ports:
      - "3306:3306"
    networks:
      - app_network
  server:
    depends_on:
      - "db" # this is important for sql connection. it has to wait for mysql to establish connection then run this bish
    build: .
    command: ["npm", "run", "dev"]
    restart: always
    ports:
      - "8000:8000"
    volumes:
      - /server/node_modules/ # added this because of bcrypt elm error
      - type: bind
        source: .
        target: /server # this name is from dockerfile workdir
    networks:
      - app_network

networks:
  app_network:
volumes:
  app_volume:

and my db Dockerfile

FROM mysql:8.0.18
EXPOSE 3306
COPY ./init_db.sql /docker-entrypoint-initdb.d/

Update

To answer @ckaserer's question:

Where are you running the migration from? e.g. are you trying to connect from your workstation/docker host to the mysql container?

I'm running it outside the container through npm with a command npm run migration:run

Any error logs you can share?

The error is saying cannot connect to db:3306, that's why I have to change it to 0.0.0.0:3306.

Can you clarify your migration workflow and where it fails? (including from which system it is executed. e.g. container x, workstation,..)

The migration happens outside the container using typeOrm cli, I create a migration, then cli will check my ormconfig.ts and will check the else block for the type and host. (I'm not sure if this is what your asking)

Update

Here's the error if I run the migration:

Error: getaddrinfo ENOTFOUND db db:3306 the IP:Port is the problem. grrrr.

Link to Code repository: https://github.com/artoodeeto/ggpo-server

2

2 Answers

2
votes

Verify

check if you can reproduce the following on your host while your containers are running.

1) start your app

simply execute docker-compose up

2) connect to db

try to connect to your mysql database from your host machine like this

mysql -u root -ppassword -h 127.0.0.1
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.18 MySQL Community Server - GPL

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
#+--------------------+
| Database           |
+--------------------+
| development_db     |
| information_schema |
| mysql              |
| performance_schema |
| production_db      |
| sys                |
| test_db            |
+--------------------+
7 rows in set (0.01 sec)

But why does that work?

A few things here. First off, your ormconfig.ts is correct for your application within the container. Your server container wants to connect to db container. That works. In your docker-compose file you explicitly exposed the db port 3306 to all available interfaces on your host. You can check that by running

docker container ls
8b5c4dc41892        ggpo-server_server   "docker-entrypoint.s…"   47 hours ago        Up 5 seconds        0.0.0.0:8000->8000/tcp              ggpo-server_server_1
a0ca405d8020        ggpo-server_db       "docker-entrypoint.s…"   47 hours ago        Up 6 seconds        0.0.0.0:3306->3306/tcp, 33060/tcp   ggpo-server_db_1

This translates to your database being available on your host at 127.0.0.1:3306 and [HOST-IP]:3306. This is why the previous mysql connect worked with 127.0.0.1.

But why does my migration not work?

Here we go! Like I stated, your ormconfig.tsis correct for your app within the container - but not if you want to connect to the database from your host. your host does not know about the DNS name db so your ormconfig.ts will not work. In order to access it from your host you need host: localhost, and for your server to work you need host: db. But you can't set both. Therefore you might want to introduce some kind of additional env config that distinguishes between the 2 scenarios.

if (process.env.NODE_ENV === 'test') {
  // test environment database
  ...
} else if (process.env.NODE_ENV === 'local') {
  // new config with host: localhost
} else {
  // default
}

Original answer from 9.01.2020

No, you can not assign a fixed IP to an image in your dockerfile. This would break portability and scalability since the IP needs to be unique.

The host binding to 0.0.0.0 is what you need in that case. It binds the mysql service to all available network interfaces. In your case to localhost and app_network within the container.

Therefore you are able to reach the container from any other container inside your app_network or you could go into the db container and execute your migration directly via mysql connection to localhost.

access to the db from your workstation

You are almost there. You already have the port exposure definition for your db in your docker-compose.yml.

version: "3.3"
services:
  db:
    ports:
      - "3306:3306"

However, you can not access the container by its name from the outside world. The port exposure definition port tells docker to forward any traffic that your host gets on 3306 to the db container on port 3306. That said, you can, therefore, access the db from your host by using your hosts IP or hostname, since you are already on the machine that runs the container you can simply use localhost or 127.0.0.1 in your migration.

You need to change the ormconfig.ts that you use for your migration. In particular the host entry value from db to localhost.

{
   "type": "mysql",
   "host": "localhost",
   "port": 3306,
   "username": "test",
   "password": "test",
   "database": "test"
}

That should fix your issue.


Bonus

1) Why can't I assign an IP to an image

An image is not running, therefore no IP assignment.
You can, however, specify a fixed IP to your container in your docker-compose.yml like this

version: '2'

services:
  app:
    image: busybox
    command: ifconfig
    networks:
      app_net:
        ipv4_address: 172.18.18.10

networks:
  app_net:
    driver: bridge
    driver_opts:
      com.docker.network.enable_ipv6: "false"
    ipam:
      driver: default
      config:
      - subnet: 172.18.18.0/24
        gateway: 172.18.18.1

2) why you should not do 1)

An IP is a unique identifier of a system in that particular network. So what happens when you would like to have 2 instances of app?

docker-compose up --scale app=2
Creating network "playground_app_net" with driver "bridge"
Creating playground_app_1 ... done
Creating playground_app_2 ... error

ERROR: for playground_app_2  Cannot start service app: b'Address already in use'

ERROR: for app  Cannot start service app: b'Address already in use'
ERROR: Encountered errors while bringing up the project.

Docker tells you that the IP address is already in use by the first instantiation of app and can not be assigned again.

1
votes

You can run both of your services in network_mode:"host" and then your both application will run on machine's localhost network. No need to map port in this case just remove ports.