Problem
I would like to request to API wordpress server(localhost:8000/wp/graphql) from frontend app by fetch function. But, the following error appeared.
Server Error
FetchError: request to http://localhost:8000/wp/graphql failed, reason: connect ECONNREFUSED 127.0.0.1:8000
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Call Stack
<unknown> (FetchError: request to
http://localhost:8000/wp/graphql%20failed,%20reason:%20connect%20ECONNREFUSED%20127.0.0.1 (8000)
Question
How does the frontend app connect to API server on docker when developing locally?
Please give me some advice.
Thank you in advance.
Infos
The following server was hosted correctly(wordpress)
localhost:8000
endpoint(/wp/graphql) is also working correctly ( from curl or browser )
The following server was hosted correctly(Next.js)
localhost:3100
Directory strategy
- Dockerfile.frontend
- docker-compose.yaml
- frontend(Next.js)
- api(worpress)
My docker-compose.yaml
version: "3"
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: wordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- ./api:/var/www/html
working_dir: "/var/www/html/wp"
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
volumes:
- ./frontend:/usr/src/app
restart: always
command: sh -c "cd app && yarn dev"
ports:
- "3100:3000"
volumes:
db_data: {}
My codes which is requesting to API(Next.js)
export async function getStaticProps({ params }) {
const data = await fetch('http://localhost:8000/wp/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
{
posts {
edges {
node {
date
title
slug
}
}
}
}
`,
}),
})
const json = await data.json()
return {
props: {
posts: json.data
}
}
}
Additional Infos
①I tried host.docker.internal. The following eror appeared
Server Error FetchError: invalid json response body at host.docker.internal:8000/wp/graphql reason: Unexpected token < in JSON at position 0
②Referencing container name directly
This shows the same error as the original error
③IP address
This also doesn’t work. Maybe the problem is that wordpress endpoint (/wp/graphql) doesn’t work by the IP addresses (127.0.0.1 and my network IP)
I’m thinking that the problem may be in communicating between containers… Thank you anyway.
5
Answers
instead of localhost, maybe you can try using host.docker.internal
so basically
Alternatively you can reference the container by name directly so
http://wordpress:8000
should work too.Lastly, you can reference it using ip address
Containers can inter-communicate using container names (e.g.
wordpress
,php
,mysql
etc.) and original ports (80
,3000
).Port mappings are used for external access – in this case: docker internal
wordpress:80
into [docker] hostlocalhost:8000
.You can check this – log into
frontend
container and run (or run ‘externally’)wget http://wordpress/index.php
. Response should look like:In my case (routing mode, internal networks) you can see the container IP and port – notice, original
80
.I think that
Unexpected token < in JSON at position 0
you get when usinghost.docker.internal.
is because wordpress is returning the home page html as a response to your next app. You could verify this by inspecting the response. The same thing should happen when you use wordpress container name in the api uri – ex.http://wordpress/graphql
This is because wordpress is unable to resolve the handler for the
/graphql
endpoint because of what is set as theWordPress Address (URL)
(settings/general or wp_options wordpress db table)If you change the WordPress Address (URL) to
http://wordpress
(your service name) it should work, but this isn’t ideal because the wordpress URL shouldn’t depend on the service name.What I did is alter the next service to use the host network mode in order to see the network interface of the docker host:
It should now be able hit the wordpress container normally without being part of the internal bridge network.
TL;DR
Use whatever you want in place of "example".
http://
must be includedEdit Windows c:/windows/system32/dirvers/etc/hosts file to add
127.0.0.1 example.com
Make some changes to your docker-compose file, by putting WordPress and nextjs in a network and giving WordPress a hostname.
If you need any clarification everything is listed out below. Enjoy!
Problem:
You have to make the fetch request to the same domain that WP thinks it is. This is written in the WP database, in the options table, row 1: siteurl. I think the problem that is occurring is that making a request to any domain other than the one shown here will trigger a url rewrite in WP(probably from .htaccess rule, apache config, or WP api add_rewrite_rule) changing the domain from whatever your fetch request is directed at to whatever is written in the database. I’m not certain if this totally scraps your original fetch request, or if it changes the origin header and since the request origin header and response origin don’t match, node-fetch throws an error (maybe someone can clarify that for me).
I’ll write down all the steps I took below, not necessarily in the order I executed them, but in the order that I would try next time. There are some steps that may be unnecessary so I’ll put those at the end and someone can respond with an updated list that doesn’t involve extra steps.
0. Flush rewrite rules:
You can do a quick search of how to do this, but I believe you go to wp-admin > settings > permalinks and click update. This is just to make sure that the /graphql endpoint is working properly.
1. Set siteurl and home DB values:
option a. You can go to your wp-admin > settings > general and change wordpress address and site address to something like http://example.com. The name you choose here can be completely arbitrary, but choose a url you never plan to visit in your browser, as we will be changing windows hosts file later, so don’t choose google.com or something like that. Anyway, make sure you include http:// as that will make it automatically use port 80.
option b. If you’re using the WP container there may be environment variables you can add to your docker-compose file that will do this for you at build time, but you’ll need to check their documentation on docker hub or github.
option c. Alternatively you could set up a phpMyAdmin container on your db network to manually change the values, which is what I did.
Your WP container probably won’t work now. That’s ok.
2. Edit your PC hosts file
If you want to be able to access your WP from its new domain name, you’ll need to tell Windows, or whatever OS, that http://example.com has a local IP and it shouldn’t try to fetch that from a DNS server.
The process to do this on Windows is: Go to windows search bar, type "notepad", right click on the notepad, run as administrator. Then go to file > open > c:/windows/system32/drivers/etc, change file type from text documents to all files and select the hosts file.
Once that file is open you will probably see a list of ip’s and hostnames. Notice that host.docker.internal is also listed here. XD
All you do is type an ip address followed by example.com(the same you used for the previous step in WP, nix the http://). Here are some options as to how you could go about this.
Use Loopback IP:
You can look up what loopback ips are currently in use on your local machine by opening CMD and typing
They’ll probably all be 127.0.0.1 on various ports. I believe you can use any 127.0.0.0/8 ip for this, meaning the zeros can be any number between 0-255, but I also won’t guarantee that.
If 127.0.0.1:80 is listed there already, perhaps try 127.12.34.156 or something(random numbers I just made up). We will need port 80 on the selected ip to be available for this to work.
OR Use Local Network Address:
If you’re connected to a router, your machine will publish open ports on your machine there to some 192.168.x.x address. You’ll have to do more research on how that’s set up, but it should be whichever 192.168 local address is listed with the 0.0.0.0 foreign address in the netstat output.
You can add this address to your hosts file like so, if it’s available.
Great. Now that that’s done you have to restart your computer. Is there a way to reinitialize the hosts file without restart? I don’t know but maybe someone else can comment.
The Reason this is a necessary step:
As mentioned in "the problem" section, WP does weird stuff to your urls. It needs to be requested from the browser as the name that it believes it’s called(maybe not always in vanilla WP, but could just be a theme/plugin issue).
Now you can start up your WP container with docker compose WITH ONE CHANGE:
You need to expose port 80 of the WP container.
We need to expose this port because we’re going to use the http:// protocol to access our WP site. There is a way apparently that you can map ports to these hosts using netsh, but you’ll have to learn about that elsewhere. That will be required if you need multiple containers to expose port 80. For our use case, we only have one container exposing port 80 so it’s fine.
So you’ve done the following:
You can now run your WP container to make sure everything is working. Once it’s up, try visiting http://example.com or whatever you named your wp. Your WP should be completely functional. If not you might need to flush rewrite rules again.
3. Network your containers
Ok, so what did we add here? By default, your docker containers will be added to the "default [bridge] network". This means that they are in their own private subnet which has its own ip address and gateway ip. The thing about this network is that you can make internal http requests to your other containers by their service name. The service names I used in the above yml file are wordpress and nextjs. So I could make a server side fetch request from the nextjs application directly to ‘http://wordpress’ and it would find that container(and http defaults to port 80, otherwise you can specify the port as well).
The problem with that is when we make such a request, the ‘wordpress’ container pulls some funny business with the rewrites or request headers, I’m not sure which, and we can’t get an appropriate response in the nextjs container.
So what we’ve done here is put our applications in some different networks. The backend network is for WP and the DB to communicate and frontend network is for Next and WP to communicate.
One very important line here is:
When we run docker-compose up, docker will add the following to the container’s /etc/hosts file
If you want to check this, spin up you containers, open CMD and type
and look at either the CONTAINER ID or NAMES column, then type
and replace the id or name with the id or name of your wordpress container. Also you might have to replace the ‘bash’ part with ‘sh’ depending on your linux distro.
What you should see in your cli is
Now type one by one
The cat command will print the hosts file contents into the cli. You should see in the list:
Where x is corresponds to your container’s ip on the docker bridge networks to which it is assigned. Since I have two networks in the above compose.yml file, mine will show two ip’s with the hostname designation– one for each network to which the container is attached; in this case "frontend" and "backend".
What’s the next step?
That’s it. You’re done.
You can now make a server-side node-fetch request like so:
A note about DNS resolution in networks.
Let me try and explain what’s going on with these hosts files as well as I understand it. If I make any mistake, someone can notify me in the comments and I’ll do my best to correct it here.
When node makes a server-side request, how it resolves the domain name specified is going to bubble outward from the container.
Upon request of http://example.com, the server will look first in its hosts file to see if there is a mention of example.com or example. If it’s there then it will resolve to whatever IP is listed. in our case there is no example.com listed in the hosts file of the nextjs container. When we set it in the wordpress container it modifies the hosts file for the wordpress container only. Well, it also defines it for the networks we set up on that container, frontend and backend.
Since the nextsjs app doesn’t have this hostname on file, the request next goes to the network. The packet will hit the network’s internet gateway. If there is a container in the network with the requested hostname(example.com) in its hosts file, the gateway will send that packet to the container. This is the case in our example.
In order to prove it, try the following. In CMD type:
Here, nextjs should be replaced with the name of your container which you can see using ‘docker ps’ command. Also the ‘sh’ option puts us in the command shell for the node-alpine image, but if you use another image you might need to change this to ‘bash’
Once in the shell you can type:
And install the traceroute package. Next do:
The output should look like this:
Let me translate:
–Selected device eth0, address 172.x.0.2, port xxxxx for outgoing packets
The address 172.x.0.2 we see here is the privat IPV4 address of the nextjs app in the frontend network.
–Tracing the path to example.com (172.x.0.3) on TCP port 80 (http), 30 hops max
The address 172.x.0.3 is the private IPV4 address of the wordpress container inside the frontend network
–1 172.x.0.3 [open] 0.099 ms 0.050 ms 0.097 ms
Took one hop, found the host at 172.x.0.3, it’s open, took only a fraction of a second.
Don’t forget to exit the shell
Conclusion
I know this is a long-winded answer but it’s really just a lot of explanation for a few simple steps:
Here is a complete docker-compose.yml for the copy-pasters:
And my Dockerfile.dev in the nextjs directory look like this:
and to run it, because next uses ‘build’
Stuck with the same problem and just solved with network aliases feature.
My
docker-compose.yml
simplified example:If you need details about
nginx.conf
please let me know