In this post we see how to get different types of network-isolated containers in LXD. Even if you are not interested in such things, doing this tutorial will help you understand better LXD proxy devices.
LXD container with no networking
To get a LXD container without networking, you omit the networking configuration in the profile that is used to create it. Therefore, we create such a profile and then use it for all our containers that have no networking.
Creating the nonetwork
profile
First, we copy the default
LXD profile as nonetwork
profile, then edit nonetwork
to remove the networking bits. We use this profile from now on to create container with no networking support.
$ lxc profile copy default nonetwork $ lxc profile show nonetwork config: {} description: Default LXD profile devices: eth0: name: eth0 nictype: bridged parent: lxdbr0 type: nic root: path: / pool: default type: disk name: nonetwork used_by: [] $ lxc profile device list nonetwork root eth0 $ lxc profile device remove nonetwork eth0 Device eth0 removed from nonetwork $ lxc profile show nonetwork config: {} description: Default LXD profile devices: root: path: / pool: default type: disk name: nonetwork used_by: [] $
As a side-note, I would like to change the description of the profile to something like Profile without networking. There is no direct command for this yet, and we need to edit the whole configuration with lxc profile edit
. To do so, run EDITOR=nano lxc profile edit nonetwork
and change the text of the description
. Save, exit, and you are done. Here is the final profile for nonetwork
.
$ lxc profile show nonetwork config: {} description: Profile without networking devices: root: path: / pool: lxd type: disk name: nonetwork used_by: [] $
Creating a nonetwork
container
We can now create a container that uses the nonetwork
profile. When we run lxc launch
, we specify the nonetwork profile, and use the default ubuntu container image (ubuntu:
) which is currently Ubuntu 18.04 LTS. In a few months this will switch to Ubuntu 20.04 LTS. We are happy with any LTS container image. If you wanted to specify specifically Ubuntu 18.04 LTS, replace ubuntu:
with ubuntu:18.04
. Finally, we give the name withoutnetworking. Once the container is created, we lxc list
it to verify there is no IP address and finally we get a shell into it with lxc ubuntu containername
.
$ lxc launch --profile nonetwork ubuntu: withoutnetworking Creating withoutnetworking The instance you are starting doesn't have any network attached to it. To create a new network, use: lxc network create To attach a network to an instance, use: lxc network attach Starting withoutnetworking $ lxc list withoutnetworking +-------------------+---------+------+-----------+ | NAME | STATE | IPV4 | TYPE | +-------------------+---------+------+-----------+ | withoutnetworking | RUNNING | | CONTAINER | +-------------------+---------+------+-----------+ $ lxc ubuntu withoutnetworking ubuntu@withoutnetworking:~$
What’s the state of networking in this container? Only loopback is there, no routes.
ubuntu@withoutnetworking:~$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
ubuntu@withoutnetworking:~$ ip route
ubuntu@withoutnetworking:~$
We have created a container without any networking. We can still get a shell into it with lxc exec
(or the handy alias lxc ubuntu
). We can move files and programs into and out of this container with lxc file push
and lxc file pull
. By doing so, we can be sure that whatever runs in this container, cannot be communicated through the network.
How to enable networking with SOCKS5
There is no networking in the container but how do we install packages? How can we add networking temporarily in some controlled way? One way is to attach a network device using lxc
commands, then remove it. Another is to use a proxy. The benefit with using a proxy is that, depending on your needs, you can switch to one that provides fine-grained control on what is being accessed. For this tutorial, we are using SOCKS5 and a SOCKS5 server running on the host. The container communicates with this proxy server over a LXD proxy device.
Creating the SOCK5 server on the host
We are using a SOCK5 server written in the Go language. Install golang
on the host, then run the following command to setup and run the server. Grab this Go file for a minimal SOCKS5 server. The filename is `simplesocke5proxyserver.go`.
$ sudo snap install go # Install Go as a snap package.
$ go get github.com/armon/go-socks5 # Install the Go package for SOCKS5
$ go run simplesocke5proxyserver.go # Run this SOCKS5 server.
Listening on 0.0.0.0:10080...
Press Ctrl+C to interrupt:
Leave this program running as long as you want the proxy server running. This specific server is unauthenticated (anyone can connect), which means that anyone on the local LAN of the host is able to use this service as an open proxy.
To verify that the SOCKS5 server is running, use the following command. It is a curl
command that connects to ubuntu.com
. The command is successful if you get any output.
$ curl -x socks5h://127.0.0.1:10080/ https://www.ubuntu.com
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>openresty/1.15.8.2</center>
</body>
</html>
$
Creating the LXD proxy device
The following command adds a proxy device to the container. The device is called socks5port10080
(arbitrary name). It connects to the loopback interface on port 10080 (where the SOCKS5 server is active) and it listens (binds) for connections on the loopback interface on port 1080. We specify that the listen/bind will happen in the container, hence the connect will be on the host. We need to specify bind=container
because if we omit it, the default is bind=host
.
$ lxc config device add withoutnetworking socks5port10080 proxy connect=tcp:127.0.0.1:10080 listen=tcp:127.0.0.1:1080 bind=container Device socks5port10080 added to withoutnetworking $
To verify that there are open ports on both the host and the container, use ss -tuna
(lsof -i
cannot see the port!?!). It should show an open port on loopback on the host at port 10080, and an open port in the container on port 1080. Here is how it looks.
$ lxc ubuntu withoutnetworking ubuntu@withoutnetworking:~$ sudo lsof -i COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME systemd-r 207 systemd-resolve 12u IPv4 915825 UDP localhost:domain systemd-r 207 systemd-resolve 13u IPv4 915826 TCP localhost:domain (LISTEN) sshd 275 root 3u IPv4 914329 TCP *:ssh (LISTEN) sshd 275 root 4u IPv6 914340 TCP *:ssh (LISTEN) ubuntu@withoutnetworking:~$ ss -tuna Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* tcp LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:* tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* tcp LISTEN 0 128 127.0.0.1:1080 0.0.0.0:* tcp LISTEN 0 128 [::]:22 [::]:* ubuntu@withoutnetworking:~$ exit $
Configuring the container to use a SOCKS5 proxy
Get a shell into the container and add the proxy to the APT configuration.
$ lxc ubuntu withoutnetworking ubuntu@withoutnetworking:~$ echo 'Acquire::http::Proxy "socks5h://localhost:1080/";' | sudo tee /etc/apt/apt.conf.d/12proxy Acquire::http::Proxy "socks5h://localhost:1080/"; ubuntu@withoutnetworking:~$
Now we can use apt
in the container. Other parts of the container cannot get access to the network unless they are configured to use a SOCKS5 client. Here we are running apt update
. Note that the command mentions the use of the proxy.
ubuntu@withoutnetworking:~$ sudo apt update 0% [Connecting to SOCKS5h proxy (socks5h://localhost:1080)] ...
In case of an error, test with the following. We connect to ubuntu.com using curl
and specifying the SOCKS5 proxy directly in the command line. Just like we did earlier on the host.
$ curl -x socks5h://localhost:1080/ https://www.ubuntu.com
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>openresty/1.15.8.2</center>
</body>
</html>
$
At this point, the container has access to the Internet only through port 1080 (service SOCKS5). When we terminate the SOCKS5 server, the access if lost.
How to setup a Web server
apt
is working from the previous step. Let’s install nginx
, then tear apart the SOCKS5 proxy, and finally create a LXD proxy device to access the Web server. By doing so, it is somewhat similar to setting up a firewall in the container (disallow traffic originating from inside the container), without using one.
We install the nginx
Web server.
ubuntu@withoutnetworking:~$ sudo apt update ubuntu@withoutnetworking:~$ sudo apt install nginx -y
Then, on the host we create a LXD proxy device to expose the Web server as port 8880 on the host (you may change this to 80, if there is no Web server already running on that port on the host). We create a proxy device called nonetwebserver
, that listens for connections on port 8880 on the host and connects them to port 80 in the container. The service (Web server) is in the container, therefore the LXD proxy device binds/listens on the host (bind=host
) in order to connect to the existing, binded, port in the container. We could omit bind=host
as it is the default to bind/listen on the host.
ubuntu@withoutnetworking:~$ exit $ lxc config device add withoutnetworking nonetwebserver proxy listen=tcp:127.0.0.1:8880 connect=tcp:127.0.0.1:80 bind=host Device nonetwebserver added to withoutnetworking
If you want to expose the Web server to your LAN, then you can replace listen=tcp:127.0.0.1:8880
with listen=tcp:0.0.0.0:8880
.
Here is a screenshot of the website. Note that I took the liberty to edit /var/www/html/index.nginx-debian.html
as shown below.

You may tear up the SOCKS5 server now. Remove the proxy device and stop the SOCKS5 server by pressing Control+C. The web server (or other service you may setup) will continue to work as long as it does not require connectivity to the Internet.
$ lxc config device remove withoutnetworking socks5port10080 Device socks5port10080 removed from withoutnetworking
$ go run simplesocks5proxyserver.go Listening on 127.0.0.1:10080... Press Ctrl+C to interrupt: ^Csignal: interrupt $
Summary
We have created a LXD container that has no Internet connectivity. We then provided temporary Internet connectivity using a SOCKS5 proxy in order to install the nginx
web server. We could have added temporarily a network interface instead, but for the purpose of this tutorial, we went full SOCKS5 proxy. You can replace our SOCKS5 proxy with another that allows to inspect the network traffic in detail.
Doing all these steps is sort of like a poor-man’s firewall. You can assume that we have setup a firewall on the container so that no incoming or outgoing traffic is allowed. A SOCKS5 proxy can selectively bypass the firewall. A proxy device may allow selective incoming traffic to the container.
If none of these interest you, you may replicate this tutorial anyway in order to practice using LXD proxy devices.
5 comments
Skip to comment form
Would the first sentence be clearer if you were to replace “it” with “the LXD proxy”?
Author
Thanks! I updated the wording in that section. Please have a look and report back whether it is better or whether it requires more attention.
Thank you! Much clearer. Very good tutorial!
This tutorial is very good! I like that you explicitly explain every step and don’t assume that your readers have any prerequisite knowledge about the topic. Have you considered submitting an article to https://www.wikihow.com/ or writing a larger series of “recipes” that might evolve into a “cookbook” like those that O’Reilly publishes (https://ssearch.oreilly.com/?q=cookbook)?
Author
Thanks! Will look into them.